Android Jenkins CI集成
Android Jenkins CI集成
Jenkins是一个独立的基于Java的程序,随时可以运行,包含Windows,Mac OS X和其他类Unix操作系统的软件包。作为可扩展的自动化服务器,Jenkins可以用作简单的CI服务器,也可以用作任何项目的持续交付中心。
环境搭建
Jenins启动及初始化
-
启动
java -jar jenkins.war --httpPort=8888
-
浏览器中通过
http://127.0.0.1:8888
访问Jenkins -
使用cat命令查看密码,并使用admin账号进行登录
cat /home/dev/.jenkins/secrets/initialAdminPassword
系统在初始化时,会下载一些必要的插件,等待下载完成即可使用。
插件管理及系统设置
Jeninks提供了大量的插件,供开发人员使用。如果未找到合适的,可自己开发相应的开源插件。
Android CI需要如下插件(根据个人及公司需要自行选择):
-
Email Extension Plugin(用于构建后邮件通知)
-
Git Parameter Plug-In(提供参数化构建)
-
Gradle Plugin(用户打包编译)
-
Locale plugin(用于汉化Jenkins)
-
Dingding[钉钉] Plugin(用于构建后使用webhook通知)
新建任务及配置
主界面
主界面选择【系统管理】-【系统设置】
系统设置】进入系统设置,配置全局变量ANDROID_HOME、汉化、Jenkins Location信息填写
ANDROID_HOME、汉化、Jenkins Location信息填写配置Git账号信息
配置Git账号信息配置邮件通知
配置邮件通知 配置邮件通知配置完成系统环境后,返回【系统管理】选择【全局工具配置】,指定JDK、Gradle及Git的环境信息
全局工具配置 全局工具配置【系统设置】及【全局工具配置】完成后,就可以创建任务进行配置我们的打包项目了
创建任务通用配置,选择参数化配置,参数化配置可选择10多种参数类型,但Git Parameter参数需要使用Git Parameter Plugins支持
主界面 主界面【源码管理】这里指定项目的git地址以及用户名和密码,并指定需要拉取源码的分支路径,这里使用了master主分支
主界面根据情况而定,如果使用了SSH登录则选择SSH username with private key(具体SSH配置自行百度)
主界面【构建触发器】构建触发器可配置何时来触发Jenkins自动执行任务,有脚本,定时任务等多种配置
【构建环境】貌似没看到太大做用在此处
【构建】构建需要指定Gradle的版本如果在【全局工具配置】中加入后该处会显示,Tasks即在使用Gradle编译时的命令:
clean assemble${PRODUCT_FLAVORS}${BUILD_TYPE} --stacktrace --debug
clean:作用是在编译前清除原始build产物
assemble${PRODUCT_FLAVORS}${BUILD_TYPE}:会拼接成为一个buildTask,例如:assembleWandoujiaDebug,该任务可参考AS右侧Gradle-:app-build中的任务列表
构建
【构建后操作】构建后可选择使用邮件通知或webhook通知,这里使用了邮件通知+钉钉的Webhook机器人通知
构建后操作 构建后操作注:钉钉的通知插件有2个目前【Dingding[钉钉]Plugin】【Dingding JSON Pusher Plugin】,后者是支持Json数据格式发送的即钉钉支持的所有消息格式,但是亲测不生效。也不知道是自己配置错误的问题。第一个则支持的其实是link消息格式。但是Link消息格式不知道如何指定@人,所以这里使用了另一种解决方法,使用[shell curl]命令进行网络请求测试发送(详见上图【构建-执行shell命令】及钉钉开放平台文档)。
配置完成后,返回主界面,进入创建的工程,选择左面的Build with Parameters进行参数化构建
参数化构建构建完成后的邮件通知:
参数化构建构建完成后的钉钉通知:
参数化构建Jenkins内建环境变量
以下是Jenkins的一些内部环境变量,在你的Jenkins项目中可以找到例如:
`
http://192.168.10.123:8888/env-vars.html
`
The following variables are available to shell scripts
BRANCH_NAME
For a multibranch project, this will be set to the name of the branch being built, for example in case you wish to deploy to production from master but not from feature branches; if corresponding to some kind of change request, the name is generally arbitrary (refer to CHANGE_ID and CHANGE_TARGET).
CHANGE_ID
For a multibranch project corresponding to some kind of change request, this will be set to the change ID, such as a pull request number, if supported; else unset.
CHANGE_URL
For a multibranch project corresponding to some kind of change request, this will be set to the change URL, if supported; else unset.
CHANGE_TITLE
For a multibranch project corresponding to some kind of change request, this will be set to the title of the change, if supported; else unset.
CHANGE_AUTHOR
For a multibranch project corresponding to some kind of change request, this will be set to the username of the author of the proposed change, if supported; else unset.
CHANGE_AUTHOR_DISPLAY_NAME
For a multibranch project corresponding to some kind of change request, this will be set to the human name of the author, if supported; else unset.
CHANGE_AUTHOR_EMAIL
For a multibranch project corresponding to some kind of change request, this will be set to the email address of the author, if supported; else unset.
CHANGE_TARGET
For a multibranch project corresponding to some kind of change request, this will be set to the target or base branch to which the change could be merged, if supported; else unset.
BUILD_NUMBER
The current build number, such as "153"
BUILD_ID
The current build ID, identical to BUILD_NUMBER for builds created in 1.597+, but a YYYY-MM-DD_hh-mm-ss timestamp for older builds
BUILD_DISPLAY_NAME
The display name of the current build, which is something like "#153" by default.
JOB_NAME
Name of the project of this build, such as "foo" or "foo/bar".
JOB_BASE_NAME
Short Name of the project of this build stripping off folder paths, such as "foo" for "bar/foo".
BUILD_TAG
String of "jenkins-${JOB_NAME}-${BUILD_NUMBER}". All forward slashes ("/") in the JOB_NAME are replaced with dashes ("-"). Convenient to put into a resource file, a jar file, etc for easier identification.
EXECUTOR_NUMBER
The unique number that identifies the current executor (among executors of the same machine) that’s carrying out this build. This is the number you see in the "build executor status", except that the number starts from 0, not 1.
NODE_NAME
Name of the agent if the build is on an agent, or "master" if run on master
NODE_LABELS
Whitespace-separated list of labels that the node is assigned.
WORKSPACE
The absolute path of the directory assigned to the build as a workspace.
JENKINS_HOME
The absolute path of the directory assigned on the master node for Jenkins to store data.
JENKINS_URL
Full URL of Jenkins, like http://server:port/jenkins/ (note: only available if Jenkins URL set in system configuration)
BUILD_URL
Full URL of this build, like http://server:port/jenkins/job/foo/15/ (Jenkins URL must be set)
JOB_URL
Full URL of this job, like http://server:port/jenkins/job/foo/ (Jenkins URL must be set)
GIT_COMMIT
The commit hash being checked out.
GIT_PREVIOUS_COMMIT
The hash of the commit last built on this branch, if any.
GIT_PREVIOUS_SUCCESSFUL_COMMIT
The hash of the commit last successfully built on this branch, if any.
GIT_BRANCH
The remote branch name, if any.
GIT_LOCAL_BRANCH
The local branch name being checked out, if applicable.
GIT_URL
The remote URL. If there are multiple, will be GIT_URL_1, GIT_URL_2, etc.
GIT_COMMITTER_NAME
The configured Git committer name, if any.
GIT_AUTHOR_NAME
The configured Git author name, if any.
GIT_COMMITTER_EMAIL
The configured Git committer email, if any.
GIT_AUTHOR_EMAIL
The configured Git author email, if any.
SVN_REVISION
Subversion revision number that's currently checked out to the workspace, such as "12345"
SVN_URL
Subversion URL that's currently checked out to the workspace.
其他配置
- 邮箱配置
不使用模板:
例如邮件配置可以指定为以下形式,
邮件由Jenkins自动发出(请勿回复!)<br />
项目名称:$PROJECT_NAME <br />
构建编号:# $BUILD_NUMBER <br />
构建状态:$BUILD_STATUS <br/>
触发原因:$CAUSE <br/>
日志地址:$BUILD_URL console <br/>
构建地址:<a href="$BUILD_URL">$BUILD_URL</a> <br/>
Apk地址:"http://192.168.10.123:8080/edu-release-v1.0.0.apk"
开发人员:<br/>
<a href="mailto:zhangsandev@gmail.com">张三</a> $nbsp;$nbsp;
<a href="mailto:lisidev@gmail.com">李四</a>
使用模板:
Jenkins默认自带groovy-html.template可以在邮件配置中使用
${SCRIPT,template="groovy-html.template"}
进行配置,如果需要自己指定样式及模板,可将模板放置在.jenkins/email-template文件夹下(没有该文件夹手动创建)邮件配置中引用该模板即可。
groovy-html.template模板内容如下(我替换了一些英文为汉字):
<STYLE>
BODY, TABLE, TD, TH, P {
font-family: Calibri, Verdana, Helvetica, sans serif;
font-size: 12px;
color: black;
}
.console {
font-family: Courier New;
}
.filesChanged {
width: 10%;
padding-left: 10px;
}
.section {
width: 100%;
border: thin black dotted;
}
.td-title-main {
color: white;
font-size: 200%;
padding-left: 5px;
font-weight: bold;
}
.td-title {
color: white;
font-size: 120%;
font-weight: bold;
padding-left: 5px;
text-transform: uppercase;
}
.td-title-tests {
font-weight: bold;
font-size: 120%;
}
.td-header-maven-module {
font-weight: bold;
font-size: 120%;
}
.td-maven-artifact {
padding-left: 5px;
}
.tr-title {
background-color: <%= (build.result == null || build.result.toString() == 'SUCCESS') ? '#27AE60' : build.result.toString() == 'FAILURE' ? '#E74C3C' : '#f4e242' %>;
}
.test {
padding-left: 20px;
}
.test-fixed {
color: #27AE60;
}
.test-failed {
color: #E74C3C;
}
</STYLE>
<BODY>
<!-- BUILD RESULT -->
<table class="section">
<tr class="tr-title">
<td class="td-title-main" colspan=2>
BUILD ${build.result ?: 'COMPLETED'}
</td>
</tr>
<tr>
<td>构建路径:</td>
<td><A href="${rooturl}${build.url}">${rooturl}${build.url}</A></td>
</tr>
<tr>
<td>项目名称:</td>
<td>${project.name}</td>
</tr>
<tr>
<td>构建日期:</td>
<td>${it.timestampString}</td>
</tr>
<tr>
<td>构建用时:</td>
<td>${build.durationString}</td>
</tr>
<tr>
<td>触发原因:</td>
<td><% build.causes.each() { cause -> %> ${cause.shortDescription} <% } %></td>
</tr>
<tr>
<td>开发人员</td>
<td>
<a href="mailto:wangxiaoliang@danyuantech.com">王晓亮</a>  :
<a href="mailto:gaopengfei@danyuantech.com">高鹏飞</a>
</td>
</tr>
<tr>
<td>Apk下载:</td>
<td><a href="http://192.168.10.123:8080/download/android/edu">下载</a></td>
</tr>
</table>
<br/>
<!-- CHANGE SET -->
<%
def changeSets = build.changeSets
if(changeSets != null) {
def hadChanges = false %>
<table class="section">
<tr class="tr-title">
<td class="td-title" colspan="2">变更记录</td>
</tr>
<% changeSets.each() {
cs_list -> cs_list.each() {
cs -> hadChanges = true %>
<tr>
<td>
修订版本
<%= cs.metaClass.hasProperty('commitId') ? cs.commitId : cs.metaClass.hasProperty('revision') ? cs.revision : cs.metaClass.hasProperty('changeNumber') ? cs.changeNumber : "" %>
by <B><%= cs.author %></B>
</td>
<td>${cs.msgAnnotated}</td>
</tr>
<% cs.affectedFiles.each() {
p -> %>
<tr>
<td class="filesChanged">${p.editType.name}</td>
<td>${p.path}</td>
</tr>
<% }
}
}
if ( !hadChanges ) { %>
<tr>
<td colspan="2">No Changes</td>
</tr>
<% } %>
</table>
<br/>
<% } %>
<!-- ARTIFACTS -->
<%
def artifacts = build.artifacts
if ( artifacts != null && artifacts.size() > 0 ) { %>
<table class="section">
<tr class="tr-title">
<td class="td-title">BUILD ARTIFACTS</td>
</tr>
<% artifacts.each() {
f -> %>
<tr>
<td>
<a href="${rooturl}${build.url}artifact/${f}">${f}</a>
</td>
</tr>
<% } %>
</table>
<br/>
<% } %>
<!-- MAVEN ARTIFACTS -->
<%
try {
def mbuilds = build.moduleBuilds
if ( mbuilds != null ) { %>
<table class="section">
<tr class="tr-title">
<td class="td-title">BUILD ARTIFACTS</td>
</tr>
<%
try {
mbuilds.each() {
m -> %>
<tr>
<td class="td-header-maven-module">${m.key.displayName}</td>
</tr>
<%
m.value.each() {
mvnbld -> def artifactz = mvnbld.artifacts
if ( artifactz != null && artifactz.size() > 0) { %>
<tr>
<td class="td-maven-artifact">
<% artifactz.each() {
f -> %>
<a href="${rooturl}${mvnbld.url}artifact/${f}">${f}</a><br/>
<% } %>
</td>
</tr>
<% }
}
}
} catch(e) {
// we don't do anything
} %>
</table>
<br/>
<% }
} catch(e) {
// we don't do anything
} %>
<!-- JUnit TEMPLATE -->
<%
def junitResultList = it.JUnitTestResult
try {
def cucumberTestResultAction = it.getAction("org.jenkinsci.plugins.cucumber.jsontestsupport.CucumberTestResultAction")
junitResultList.add( cucumberTestResultAction.getResult() )
} catch(e) {
//cucumberTestResultAction not exist in this build
}
if ( junitResultList.size() > 0 ) { %>
<table class="section">
<tr class="tr-title">
<td class="td-title" colspan="5">${junitResultList.first().displayName}</td>
</tr>
<tr>
<td class="td-title-tests">Name</td>
<td class="td-title-tests">Failed</td>
<td class="td-title-tests">Passed</td>
<td class="td-title-tests">Skipped</td>
<td class="td-title-tests">Total</td>
</tr>
<% junitResultList.each {
junitResult -> junitResult.getChildren().each {
packageResult -> %>
<tr>
<td>${packageResult.getName()}</td>
<td>${packageResult.getFailCount()}</td>
<td>${packageResult.getPassCount()}</td>
<td>${packageResult.getSkipCount()}</td>
<td>${packageResult.getPassCount() + packageResult.getFailCount() + packageResult.getSkipCount()}</td>
</tr>
<% packageResult.getPassedTests().findAll({it.getStatus().toString() == "FIXED";}).each{
test -> %>
<tr>
<td class="test test-fixed" colspan="5">
${test.getFullName()} ${test.getStatus()}
</td>
</tr>
<% } %>
<% packageResult.getFailedTests().sort({a,b -> a.getAge() <=> b.getAge()}).each{
failed_test -> %>
<tr>
<td class="test test-failed" colspan="5">
${failed_test.getFullName()} (Age: ${failed_test.getAge()})
</td>
</tr>
<% }
}
} %>
</table>
<br/>
<% } %>
<!-- CONSOLE OUTPUT -->
<%
if ( build.result == hudson.model.Result.FAILURE ) { %>
<table class="section" cellpadding="0" cellspacing="0">
<tr class="tr-title">
<td class="td-title">控制台输出</td>
</tr>
<% build.getLog(100).each() {
line -> %>
<tr>
<td class="console">${org.apache.commons.lang.StringEscapeUtils.escapeHtml(line)}</td>
</tr>
<% } %>
</table>
<br/>
<% } %>
</BODY>
-
构建后配置-归档成品:该配置只适用于将成品生成到workspaces下,其他路径不生效,归档成品后会在工程页面展示成品信息
-
构建后发布到蒲公英:文档
参考文章: