先到Jenkins官网的Meet Jekins中看一下Installation部分,原文如下
You have several options for downloading and installing Jenkins:
*Use one of the platform-specific package/installer links on the Jenkins site to install Jenkins on your system.
*You can download jenkins.war directly and launch it by executing java -jar jenkins.war. This is basically the same set up as the test drive, except that the output will go to console, not to a window. On Windows, you can even choose to install Jenkins as a service afterwards.
*If you have a servlet container that supports Servlet 2.4/JSP 2.0 or later, such as Tomcat 5, you can deploy jenkins.war as you would any WAR file. See this document for more about container-specific installation instruction.
大概的看一下,意思就是说有好几种方法下载和安装Jenkins,针对windows操作系统可以选择的方法是,下载Jenkins提供的exe直接安装,以服务的方式运行,还有一种是下载Jenkins提供的war,war文件需要通过tomcat安装,还有许多的配置项需要设置,简单起见,本文采用exe的方式进行安装,下载完成之后解压出来,运行setup.exe,安装完成之后进入控制面板->管理工具->服务就可以看到jenkins。通过http://localhost:8080/就可以进入到jenkins的web页面了,如果需要配置jenkins的工作目录,先停止jenkins服务,然后再增加环境变量JENKINS_HOME即可。
还有几个插件要安装一下,进入到系统管理->管理插件->可选插件,安装一下Email Extension Plugin(用于邮件通知)和Gradle plugin(用于gradle编译)这两个插件。安装完成之后,再配置一些信息
- 进入到系统管理->系统设置,修改系统设置
Jenkins Location标签中
系统管理员邮件地址:[email protected]
邮件通知标签中
SMTP服务器:smtp.163.com
用户名:sender
密码:xxxxxx
SMTP端口:选中SSL填465,没选中填25
Reply-To Address:[email protected]
字符集:UTF-8
- 项目设置
高级项目选项标签中
使用自定义的工作空间:可以配置自定义的工作空间
源码管理标签中Subversion
Repository URL: svn地址,注意这里都要填写小写的字母,否则如果真是svn路径中有大写字母的话,会导致svn版本号获取不到以及变更集获取不到
Local modle directory(optional):.
Repository depth:infinity
Check out Strategy:use 'svn update' as much as possible
构建标签中
Gradle Version:选中最新版本吧,我选的是2.9
Tasks:clean buildAll,这个是build.gradle中的任务,后面会把测试工程的build.gradle放出来
构建后操作步骤中加入Archive the artifacts,Email Notificatioin,Editable Email Notification
Archive the artifacts标签中
用于存档的文件:app/build/release/*.zip
Email Notificatioin标签中
Recipients:收件人的名字,这里是配置发送编译错误以及编译恢复邮件的收件人
Editable Email Notification标签中要增加触发器,成功后发送给Rccipient List
Project Recipient List:[email protected],[email protected],...,[email protected]
Project Reply-To List:[email protected]
Content Type:HTML(text/html)
Default Subject:构建通知:$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!
Default Content:
(本邮件是程序自动下发的,请勿回复!)
项目名称:$PROJECT_NAME
构建编号:$BUILD_NUMBER
svn版本号:${SVN_REVISION}
构建状态:$BUILD_STATUS
触发原因:${CAUSE}
构建日志地址:${BUILD_URL}console
构建地址:$BUILD_URL
变更集:${JELLY_SCRIPT,template="html"}
这里是分割线,服务器的配置都ok了,Android Studio的工程当然也是要做一些配合才可以完成的,下面把Android Studio工程中app的build.gradle贴出来
import org.tmatesoft.svn.core.wc.*
import org.tmatesoft.svn.core.wc2.*
import org.tmatesoft.svn.core.*
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
// 包名
applicationId "com.zhb.studiotest"
minSdkVersion 19
targetSdkVersion 22
versionCode 1
versionName "1.0.0.1"
manifestPlaceholders = [ CHANNEL_NAME:"Unspecified",APPLICATION_LABLE:"StudioTest"]
}
sourceSets.main.jni.srcDirs = []
sourceSets.main.jniLibs.srcDir 'src/main/libs'
def versionPropsFile = file('version.properties')
if (versionPropsFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def prename = versionProps['VERSION_NAME_MAJOR']
def name = versionProps['VERSION_NAME_BUILD'].toInteger()
def runTasks = gradle.startParameter.taskNames
if ('buildAll' in runTasks) {
name++
}
versionProps['VERSION_NAME_BUILD']=name.toString()
versionProps.store(versionPropsFile.newWriter(), null)
defaultConfig {
versionName prename + name
}
}
signingConfigs {
releaseConfig {
// 写死签名密码
keyAlias 'xxx'
keyPassword 'xxxx'
storeFile file("keystore.jks")
storePassword 'xxxx'
// 要求输入签名密码
// storeFile file("keystore.jks")
// keyAlias System.console().readLine("\nkeyAlias: ")
// storePassword System.console().readLine("\nKeystore password: ")
// keyPassword System.console().readLine("\nKey password: ")
}
}
/*productFlavors {
xiaomi {
applicationId = "com.zhb.xiaomi"
manifestPlaceholders = [UMENG_CHANNEL_VALUE: name,APPLICATION_LABLE:name]
}
baidu {
applicationId = "com.zhb.baidu"
manifestPlaceholders = [UMENG_CHANNEL_VALUE: name,APPLICATION_LABLE:name]
}
wandoujia {
applicationId = "com.zhb.wandoujia"
manifestPlaceholders = [UMENG_CHANNEL_VALUE: name,APPLICATION_LABLE:name]
}
}*/
productFlavors {
wandoujia {}
baidu {}
//c360 {}
//uc {}
productFlavors.all { flavor ->
applicationId = "com.zhb."+name
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name,APPLICATION_LABLE:name]
}
}
buildTypes {
debug {
buildConfigField "boolean", "LOG_DEBUG", "true"
versionNameSuffix "-debug"
// 混淆开关
minifyEnabled false
// 在Android中,每个应用程序中储存的数据文件都会被多个进程访问:
// 安装程序会读取应用程序的manifest文件来处理与之相关的权限问题;
// Home应用程序会读取资源文件来获取应用程序的名和图标;
// 系统服务会因为很多种原因读取资源(例如,显示应用程序的Notification);
// 此外,就是应用程序自身用到资源文件。
// 当资源文件通过内存映射对齐到4字节边界时,访问资源文件的代码才是有效率的。
zipAlignEnabled false
// 删除没用的资源文件
shrinkResources false
}
release {
buildConfigField "boolean", "LOG_DEBUG", "false"
minifyEnabled true
zipAlignEnabled true
shrinkResources true
// 混淆文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// 签名
signingConfig signingConfigs.releaseConfig
applicationVariants.all { variant ->
// 修改APK名称
variant.outputs.each { output ->
def file = output.outputFile
def fileName = file.name
fileName = fileName.replace(".apk", "-V${defaultConfig.versionName}.apk")
fileName = fileName.replace("app", "StudioTest")
fileName = fileName.replace("debug-unaligned", "debug")
output.outputFile = new File(file.parent, fileName)
}
// 修改values.xml
variant.mergeResources.doLast(){
File valuesFile = file("${buildDir}/intermediates/res/merged/${variant.dirName}/values/values.xml")
String content = valuesFile.getText('UTF-8')
content = content.replaceAll("CHANNEL_NAME","${variant.productFlavors[0].name}")
valuesFile.write(content,'UTF-8')
}
}
}
}
}
def getSvnRevision(){
ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
SVNClientManager clientManager = SVNClientManager.newInstance(options);
SVNStatusClient statusClient = clientManager.getStatusClient();
SVNStatus status = statusClient.doStatus(project.rootDir, false);
SVNRevision revision = status.getRevision();
return revision.getNumber();
}
task svnCommitVersionFile(){
description = "Commits a single file to an SVN repository"
doLast{
if (!project.hasProperty("commitMsg")){
ext.commitMsg = "//change version"
}
SvnOperationFactory svnOperationFactory = new SvnOperationFactory()
def authentication = SVNWCUtil.createDefaultAuthenticationManager("haibo.zhou", "hbzhou0622")
svnOperationFactory.setAuthenticationManager(authentication)
try {
SvnCommit commit = svnOperationFactory.createCommit()
commit.setSingleTarget(SvnTarget.fromFile(new File('app/version.properties')))
commit.setCommitMessage(commitMsg)
SVNCommitInfo commitInfo = commit.run()
println "Commit info: " + commitInfo
println "Commit message: " + commitMsg
} finally{
svnOperationFactory.dispose()
}
}
}
task generateZip(type: Zip){
def versionPropsFile = file('version.properties')
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionPropsFile))
def prename = versionProps['VERSION_NAME_MAJOR']
def name = versionProps['VERSION_NAME_BUILD'].toInteger()
def version = "V" + prename + name + "_(" + getSvnRevision() + ")"
from 'build/outputs'
archiveName "StudioTest_" + version + ".zip"
destinationDir file("build/release")
doLast(){
copy{
from ("build/release/"+archiveName)
into ("release")
}
if (!project.hasProperty("commitMsg")){
ext.commitMsg = "//upload compile result"
}
SvnOperationFactory svnOperationFactory = new SvnOperationFactory()
def authentication = SVNWCUtil.createDefaultAuthenticationManager("haibo.zhou", "hbzhou0622")
svnOperationFactory.setAuthenticationManager(authentication)
try {
SvnScheduleForAddition add = svnOperationFactory.createScheduleForAddition();
SvnTarget target = SvnTarget.fromFile(new File("app/release/"+archiveName));
add.addTarget(target);
add.setAddParents(true);
add.setForce(true);
add.run();
SvnCommit commit = svnOperationFactory.createCommit()
commit.setSingleTarget(SvnTarget.fromFile(new File("app/release/"+archiveName)))
commit.setCommitMessage(commitMsg)
SVNCommitInfo commitInfo = commit.run()
println "Commit info: " + commitInfo
println "Commit message: " + commitMsg
} finally{
svnOperationFactory.dispose()
}
}
}
// build script for jenkins only
task buildAll(){
println 'start build'
}
svnCommitVersionFile.dependsOn build
generateZip.dependsOn svnCommitVersionFile
buildAll.dependsOn generateZip
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
task ndkBuild(type: Exec) {
workingDir file('src/main/jni')
commandLine getNdkBuildCmd()
}
task cleanNative(type: Exec){
workingDir file('src/main/jni')
commandLine getNdkBuildCmd(), 'clean'
}
clean.dependsOn cleanNative
def getNdkDir() {
if (System.env.ANDROID_NDK_ROOT != null)
return System.env.ANDROID_NDK_ROOT
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def ndkdir = properties.getProperty('ndk.dir', null)
if (ndkdir == null)
throw new GradleException("NDK location not found. Define location with ndk.dir in the local.properties file or with an ANDROID_NDK_ROOT environment variable.")
return ndkdir
}
def getNdkBuildCmd() {
def ndkbuild = getNdkDir() + "/ndk-build"
ndkbuild += ".cmd"
return ndkbuild
}
dependencies {
// 工程目录里面的libs文件夹下所有的jar包
compile fileTree(dir: 'libs', include: ['*.jar'])
// 网络仓库里面的工程
//compile 'com.github.chrisbanes.photoview:library:1.2.4'
// 本地的工程
compile project(':PhotoView-master')
}
大概描述一下脚本,在控制台运行gradle buildAll,将会把build/output目录打成压缩生成到build/release目录,并自动修改版本号文件version.property提交到svn,同时将编译结果提交到svn。
ok,在到我们的jenkins里面,编译一下,结果生成了,看一下我们收到的邮件
编译日志,编译结果,上一次构建到这次构建的svn提交记录都可以看到了,作为一个简单的持续集成环境还是可以的。
2016.01.16
------End------