jenkins+svn+android studio自动化构建(持续集成)

  先到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里面,编译一下,结果生成了,看一下我们收到的邮件

jenkins+svn+android studio自动化构建(持续集成)_第1张图片

  编译日志,编译结果,上一次构建到这次构建的svn提交记录都可以看到了,作为一个简单的持续集成环境还是可以的。


2016.01.16

------End------

转载于:https://www.cnblogs.com/zhouhaibo/p/5135620.html

你可能感兴趣的:(jenkins+svn+android studio自动化构建(持续集成))