android+jenkins+gradle+lint+checkstyle+findbugs+邮件附件报告

打造最实用的Android持续集成(Continuous Integration以下简称CI)系统

什么是CI

纵览全局(打破职责界限)

软件开发、运维和质量保证三个部门之间的沟通、协作和集成所采用的流程、方法和体系的一个集合。
打破目前的rd->qa->op流水线的流程,而是将三者紧密的结合在一起。从实践的结果来看,rd每次提交代码都会触发一系列的自动化步骤,包括编译,单元测试,代码覆盖率,功能测试,部署测试,性能/容量测试(注:后两者受限与时间要求,实际实施不会每次提交代码都触发)。Rd,qa,op都在过程中做质量保障。

代码树被管理起来——主干开发

主干开发的好处是每个rd都知晓整体的变更,所有的feature作为一个整体发布,对OP的现实意义就是上线变得更有规律,非计划的、临时的上线最后消失。
代码和周边(配置,数据,构建脚本,单元测试,测试用例)统一作为产品被管理起来——一键式产构建,测试,部署,完成产品的最终发布。
大家都在一个平台上工作,所有的任务都在这个平台下,各角色间对互相的工作有更深入的了解,并且,工作状态也可以共享。
少就是多,简洁就是美(用简单的方法解决问题)
持续集成的解决方案是简洁的。产品由SVN去管理,构建过程由CI server负责,而构建过程包含了编译,测试,发布,部署过程

容量测量(Capacity management)

容量的变化体现在用户行为(流量)系统变更(软件性能)和资源(服务器数量,冗余度计划)等几个因素的变化上,将容量和这些变化挂钩,在每一个因素变化下重新得到系统的容量,从而在变更中控制容量不足造成的风险。有一个要点,我们需要的是系统的容量而不是单个模块的性能。

质量反馈(Quality feedback)

变更会导致质量变化,而质量变化体现在各种指标上,而测量这些指标(包括应用指标:平响,处理效率等和系统指标:负载,网络流量),发现指标之间的规律,将指标share给整个团队,从而有效的达成质量的反馈,控制变更(包括内部变更和外部条件的变化)造成的质量下降的风险。

Jenkins能做什么

在实施持续集成的过程中,并行实施的三个项目:
持续部署/一键式部署(continuous deployment/one step deploy),
容量测试/管理(Capacity Test/Management)
质量反馈(Quality feedback)
Jenkins将完成上面3个并行的项目工作

安装与配置Jenkins

gradle代码

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    signingConfigs {
        mySign {
            keyAlias 'xxx'
            keyPassword 'xxx'
            storeFile file('hqyx.keystore')
            storePassword 'xxx'
            v2SigningEnabled false
        }
    }
    compileSdkVersion 26

    defaultConfig {
        applicationId "com.hqyxjy.word"
        minSdkVersion 19
        targetSdkVersion 21
        versionCode 7
        versionName "1.1.0"
        multiDexEnabled = true

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        resValue('string', 'git_last_commit', 'git log --pretty=format:"%H:%ad:%an:%s" --date=short -1'.execute().text)
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.mySign
        }
        debug {
            signingConfig signingConfigs.mySign
        }
    }

    productFlavors {
        flavorDimensions 'channel', 'host', 'area'

        // 渠道
        hqyx {
            dimension 'channel'
            manifestPlaceholders.put('PACKAGE_CHANNEL_VALUE', 'hqyx')
        }

        develop {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.develop'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.develop'])
        }

        // host的值
        ttest {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.test'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.test'])
        }

        sim {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.sim'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.sim'])
        }
        beta {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.beta'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.beta'])
        }
        online {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.online'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.online'])
        }
        develop {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.develop'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.develop'])
        }

        // area
        outside {
            dimension 'area'
            buildConfigField('boolean', 'IS_INSIDE', 'false')
        }
        inside {
            dimension 'area'
            buildConfigField('boolean', 'IS_INSIDE', 'true')
        }
    }
    // 控制打包时输出的文件名
    android.applicationVariants.all { variant ->
        variant.outputs.all {
            if (!variant.buildType.debuggable) {
                // 计算出新的文件名格式:NewName_v1.3.0_02051437_develop.apk
                def fileName = "NewName_${defaultConfig.versionName}_${new Date().format("MMddHHmm")}_${variant.flavorName}.apk"
                fileName = fileName.replace("_z_", "_")
                outputFileName = fileName
            }
        }
    }

    // 配置多渠道时,去除没用的Variants
    android.variantFilter { variant ->
        if (variant.buildType.name.contains('debug')) {
            variant.getFlavors().each() { flavor ->
                if (flavor.name.contains('z_')) {
                    variant.setIgnore(true)
                }
            }
        } else {
            variant.getFlavors().each() { flavor ->
                if (flavor.name.contains('develop')) {
                    variant.setIgnore(true)
                }
            }
        }
    }
    lintOptions {
        //发生错误时停止构建
        abortOnError true
        //警告都提升为错误
        warningsAsErrors true
        //build release 版本 时 开启lint 检测
        checkReleaseBuilds true
        //规则位置
        lintConfig file("${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/lint.xml")
        //不生成xml报告
        xmlReport false
        //忽略
        disable 'OldTargetApi'
        //设置报告位置
        htmlOutput file("${project.rootDir}/ci/report/app/lint_report.html")
    }
    compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:support-v4:26.1.0'
    implementation 'com.google.android.gms:play-services-plus:11.6.2'
    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testImplementation files('powermocklibs/byte-buddy-1.2.1.jar')
    testImplementation files('powermocklibs/cglib-nodep-2.2.2.jar')
    testImplementation files('powermocklibs/hamcrest-core-1.3.jar')
    testImplementation files('powermocklibs/javassist-3.21.0-GA.jar')
    testImplementation files('powermocklibs/mockito-core-2.0.42-beta.jar')
    testImplementation files('powermocklibs/objenesis-2.4.jar')
    testImplementation files('powermocklibs/powermock-api-mockito-common-1.6.6.jar')
    testImplementation files('powermocklibs/powermock-mockito2-1.6.6-full.jar')
    debugImplementation 'com.amitshekhar.android:debug-db:1.0.1'

    implementation project(':AndroidCoreLibrary:core')

    implementation project(path: ':svprogresshud')
    testImplementation 'junit:junit:4.12'
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:design:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.android.support:multidex:1.0.1'
    implementation 'com.jakewharton:butterknife:8.4.0'
    implementation 'com.android.support:gridlayout-v7:26.1.0'
    implementation 'cn.yipianfengye.android:zxing-library:2.1'
    testCompile 'junit:junit:4.12'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
    debugImplementation 'com.amitshekhar.android:debug-db:1.0.1'
    implementation 'org.greenrobot:eventbus:3.0.0'
    implementation 'com.github.traex.rippleeffect:library:1.3'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"

    dependencies {
        debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
        releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
    }

    //友盟统计
    implementation 'com.umeng.analytics:analytics:latest.integration'
}
repositories {
    mavenCentral()
}
/*------------------------------------------------------------------------------------------------*/
/*                                         CI TASK
/*------------------------------------------------------------------------------------------------*/

apply plugin: 'pmd'
task pmd(type: Pmd) {
    ruleSetFiles = files("${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/pmd_library_ruleset.xml",
            "${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/pmd_custom_ruleset.xml",
            "${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/pmd_single_ignore_ruleset_naming_VariableNamingConventions.xml")
    ignoreFailures = false
    ruleSets = []

    source 'src'
    include '**/*.java'
    exclude '**/gen/**'

    reports {
        xml.enabled = false
        html.enabled = true
        html {
            destination "${project.rootDir}/ci/report/app/pmd_report.html"
        }
    }
}

apply plugin: 'findbugs'
task findbugs(type: FindBugs) {
    ignoreFailures = false
    effort = "default"
    reportLevel = "medium"
    //过滤器
    excludeFilter = new File("${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/findbug_filter.xml")
    //这里填写项目classes目录
    classes = files("${project.buildDir}/intermediates/classes")
    source = fileTree('src/main/java')
    classpath = files()
    reports {
        //只能开启一个
        xml.enabled = false
        html.enabled = true
        html {
            destination "${project.rootDir}/ci/report/app/find_bugs_report.html"
        }
    }
}

apply plugin: 'checkstyle'

checkstyle {
    toolVersion '7.1.2'
    showViolations false
}

task checkstyle(type: Checkstyle) {
    ignoreFailures = true
    def config = file("${project.rootDir}/ci/ruleset/checkstyle.xml")
    if (config.exists()) {
        ignoreFailures = false
        configFile = config

        classpath = files()
        source = android.sourceSets.main.java.srcDirs
        include '**/*.java'
        include '**/*.xml'

        reports {
            xml.enabled = false
            html.enabled = true
            html {
                destination "${project.rootDir}/ci/report/app/check_style_report.html"
            }
        }
    }
}

代码分析

运行效果

自定义邮件报告格式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容

  • <<互联网敏捷DevOps和自动化之5.持续集成>>持续集成的价值是什么?对于开发和测试人员又意味着什么呢?1.1...
    燕京博士阅读 2,788评论 0 5
  • 质量综述 随着互联网快速发展,早期的传统软件公司强调工程的严谨性,CMMI,ISO9000格局已经发生变化,逐渐退...
    老余2017阅读 3,926评论 6 31
  • 在前一篇文章持续集成入门篇中我大概介绍了下持续集成的概念及工具(抱歉,在前一篇文章中我查的资料不够与时俱进,工具介...
    craneyuan阅读 1,770评论 0 7
  • 《美丽新世界》被认为和《1984》、《动物庄园》齐名的反乌托邦小说。描绘的是在未来的一个大同世界,消灭了衰老、疾病...
    人在旅途_Amber阅读 715评论 0 1
  • 看得太真切, 会对世界感到沮丧。 即便是真的, 又何必去捅破? 情愿是假的, 依然会对世界充满向往。 可有一处静谧...
    Electrician阅读 217评论 0 2