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"
            }
        }
    }
}

代碼分析

運行效果

自定義郵件報告格式

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • <<互聯網敏捷DevOps和自動化之5.持續集成>>持續集成的價值是什么?對于開發和測試人員又意味著什么呢?1.1...
    燕京博士閱讀 2,813評論 0 5
  • 質量綜述 隨著互聯網快速發展,早期的傳統軟件公司強調工程的嚴謹性,CMMI,ISO9000格局已經發生變化,逐漸退...
    老余2017閱讀 3,965評論 6 31
  • 在前一篇文章持續集成入門篇中我大概介紹了下持續集成的概念及工具(抱歉,在前一篇文章中我查的資料不夠與時俱進,工具介...
    craneyuan閱讀 1,786評論 0 7
  • 《美麗新世界》被認為和《1984》、《動物莊園》齊名的反烏托邦小說。描繪的是在未來的一個大同世界,消滅了衰老、疾病...
    人在旅途_Amber閱讀 754評論 0 1
  • 看得太真切, 會對世界感到沮喪。 即便是真的, 又何必去捅破? 情愿是假的, 依然會對世界充滿向往。 可有一處靜謐...
    Electrician閱讀 221評論 0 2