在多項目工程中統(tǒng)計子工程的覆蓋率

背景介紹

閱讀此文請優(yōu)先確保已讀懂Gradle構(gòu)建系統(tǒng)簡介及在Gradle中集成覆蓋率工具Jacoco并使用

在前文中我們對如何在gradle編譯體系的工程中加入Jacoco代碼覆蓋率統(tǒng)計的方法做了介紹,但是前文的方法僅能統(tǒng)計到主工程的代碼覆蓋率,而無法統(tǒng)計到庫工程,其具體原因可以參考此文Issue 76373: Code Coverage does not work for library project,簡單總結(jié)一下就是目前google提供的android plugin有bug(或者是設計如此):所有的庫工程都會使用Release版本來進行編譯,即使你聲明了TestCompile,而Release版本是無法用于統(tǒng)計代碼覆蓋率的,因此我們需要一些手段讓編譯系統(tǒng)能夠編譯出Debug版本的庫工程,參考資料中提供了一個不錯的思路,我們在向Dolphin中植入時會遇到一些問題,在此將整個過程詳細記錄下來,以供參考。

實踐與經(jīng)驗

先介紹一下整體思路再分步詳述。

要完成植入我們大概需要做如下的幾件事:

  • 在所有庫工程都引入了的DolphinBuild/common.gradle內(nèi)增加配置項,打開debug模式下的覆蓋率統(tǒng)計,同時讓所有工程都能產(chǎn)生debug版本的編譯結(jié)果
  • 處理主工程及所有子工程(包括嵌套的子工程)中的依賴關系,使用debug的configuration來進行編譯
  • 處理插樁時可能存在的jacocoangent.jar重復的問題,刪除重復的jar包
  • 處理jacocoReport任務,擴展需要分析的文件的范圍

配置共用腳本common.gradle

觀察Dolphin所有子工程的build.gradle腳本,發(fā)現(xiàn)都會有這樣一行
apply from: "$project.rootDir/DolphinBuild/common.gradle"
意味著所有的腳本都會引用這個文件,通過修改這個文件我們可以直接修改到所有工程的build行為,而不用一一去更改每個build.gradle文件

為了能在所有工程中都能跑代碼覆蓋率,我們需要添加在debug版本下對覆蓋率的支持:

android {
    buildTypes {
        debug {
            testCoverageEnabled true
        }
    }
}

為了讓庫工程能夠編譯輸出Debug版本,我們需要增加如下的部分,可以參考:http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library|Referencing-a-Library:

android {
    publishNonDefault true
}

至此,這個共用文件的修改就完成了

處理依賴,使用debug編譯庫工程

如引文中所述,我們需要將所有的依賴項從:
compile project(':DolphinCoreLibrary')
的形式改變?yōu)?br> debugCompile project(path:':DolphinCoreLibrary', configuration: 'debug')
的形式

如果是少數(shù)幾個文件,慢慢替換即可,可是像Dolphin這樣擁有茫茫多庫工程和相互依賴的項目來說,通過腳本一次性完成替換是必須的。可以在工程根目錄(即shel_en_agile下)運行命令,內(nèi)容如下:

find ./ -name build.gradle | xargs sed -i "s/^\(.*\)compile project(':\(.*\))/\1debugCompile project(path:':\2, configuration: 'debug')/g"

不要小看這小小的一行命令,里面有著多個重要的知識點,讓我們娓娓道來(明白的同學和不關心細節(jié)可以自行跳過啦,這些知識和本文無關):

  • find命令是linux下的查找命令,-name的參數(shù)聲明我們要在當前目錄(./)下查找所有名字為build.gradle的文件,如果僅執(zhí)行find命令,輸出的是該目錄下所有滿足條件的文件的路徑
  • |是linux下的重定向符,將前面命令的結(jié)果作為參數(shù)傳遞給后面的命令
  • xargs命令的作用是將參數(shù)列表轉(zhuǎn)換成小塊分段傳遞給其他命令(這里傳遞的目標是sed命令)
  • sed命令是一個簡單的對文件逐行處理的程序,支持正則表達式,-i的參數(shù)表示操作會直接在文件中生效而不是顯示在控制臺上,其后的參數(shù)中s表示這次執(zhí)行替換操作,/是分割符,分開了需要被替換的部分和替換的目標,g表示全局替換,會替換全部的匹配項
  • 正則表達式分成了兩個部分,匹配部分為<color red>^(.)compile project(':(.))</color>,匹配包含compile project且結(jié)尾為")"的行,兩組括號(已轉(zhuǎn)義)表示需要提取的group在替換時使用。替換部分為<color red>\1debugCompile project(path:':\2, configuration: 'debug')</color>,表示替換的目標其中的\1和\2表示之前匹配的兩個group,其余部分用定義的文字替換

處理插樁時可能存在的jacocoangent.jar重復的問題,刪除重復的jar包

我們嘗試使用處理過的包進行編譯是發(fā)現(xiàn)報錯了:

Execution failed for task ':DolphinBrowserEN:proguardDebug'.
> java.io.IOException: Can't write [/home/pgao/dolphin/src/shell_en_agile/DolphinBrowserEN/build/intermediates/classes-proguard/debug/classes.jar] (Can't read [/home/pgao/dolphin/src/shell_en_agile/DolphinBrowserEN/build/intermediates/exploded-aar/shell_en_agile.common_library/ui_utils/unspecified/debug/libs/jacocoagent.jar(;;;;;;!META-INF/MANIFEST.MF)] (Duplicate zip entry [jacocoagent.jar:com/vladium/emma/rt/RT.class]))

原因是主工程已經(jīng)集成了jacoco,庫工程又集成會導致同時存在了多個jacocoagent.jar文件,我們需要在執(zhí)行proguardDebug任務前刪除多余的jacocoagent.jar,編譯就可以繼續(xù)進行了,需要在主工程的build.gradle中增加如下的腳本(先貼總腳本,再講解過程):

task deleteJacocoagentJar {
    doLast {
        getTransitiveProjectDependencies(this, 'debugCompile').each { project ->
//            println "**********" + "build/intermediates/exploded-aar/${rootProject.name}.services/${project.name}/unspecified/debug/libs/jacocoagent.jar" + "***********"
            delete "build/intermediates/exploded-aar/${rootProject.name}.services/${project.name}/unspecified/debug/libs/jacocoagent.jar"
            delete "build/intermediates/exploded-aar/${rootProject.name}.common_library/${project.name}/unspecified/debug/libs/jacocoagent.jar"
            delete "build/intermediates/exploded-aar/${rootProject.name}.third_party/${project.name}/unspecified/debug/libs/jacocoagent.jar"
            delete "build/intermediates/exploded-aar/${rootProject.name}/${project.name}/unspecified/debug/libs/jacocoagent.jar"
            delete "build/intermediates/exploded-aar/shell_en_agile.third_party.animator/library/unspecified/debug/libs/jacocoagent.jar"
            delete "build/intermediates/exploded-aar/shell_en_agile.third_party.svg-android/svgandroid/unspecified/debug/libs/jacocoagent.jar"
            delete "build/intermediates/exploded-aar/shell_en_agile.services.promotion_service/promotion_link/unspecified/debug/libs/jacocoagent.jar"
            //delete "/home/pgao/dolphin/src/topstory/DolphinNewsClient/build/intermediates/exploded-aar/topstory.services/news_service/unspecified/debug/libs/jacocoagent.jar"
        }
    }
}

def getTransitiveProjectDependencies(project, configuration) {
    def projectDependencies = project.configurations."$configuration".getAllDependencies().withType(ProjectDependency)
    def dependencyProjects = projectDependencies*.dependencyProject
    dependencyProjects.each {
        dependencyProjects += getTransitiveProjectDependencies(it, configuration)
    }
    return dependencyProjects.unique()
}

android {
    applicationVariants.all { variant ->
        variant.dex.dependsOn deleteJacocoagentJar
        deleteJacocoagentJar.mustRunAfter variant.javaCompile
    }

    testVariants.all { variant ->
        variant.dex.dependsOn deleteJacocoagentJar
        deleteJacocoagentJar.mustRunAfter variant.javaCompile
    }
}

我們定義了一個名字為:deleteJacocoagentJar的任務,和一個getTransitiveProjectDependencies的方法,并將這個任務綁定到了javaCompile任務之后強制執(zhí)行。

getTransitiveProjectDependencies

這是我們自定義的一個方法,作用是獲取到所有依賴項(包括遞歸依賴項)的工程,后續(xù)會把這些工程對應在主工程內(nèi)生成的jacocoagent.jar刪除,獲取這個工程列表分成了4步

  • 獲取當前工程全部的依賴項
  • 針對每一個依賴項獲取其工程,并加入列表
  • 遞歸處理每一個庫工程,將所有的依賴工程加入列表
  • 針對列表做去重處理并返回

deleteJacocoagentJar任務

在這個任務中我們針對之前獲取的全部依賴項做刪除處理,由于暫時沒有弄清楚build/intermediates/exploded-aar下不同的工程編譯文件生成的不同路徑的原理,因此我暫時也沒有找到一條通用的規(guī)則適配到全部的工程上,只能根據(jù)編譯結(jié)果修改不同的模式,在瀏覽器工程下我總結(jié)了7條規(guī)則,如果要針對其他的項目做移植,需要自己判斷是否需要對規(guī)則進行修改,甚至直接列出每一個工程的直接路徑。后續(xù)在調(diào)查清楚這些文件生成的邏輯后可能會更新此塊的內(nèi)容。

將這個任務綁定到了javaCompile任務之后強制執(zhí)行

我們定義的任務是需要被自動執(zhí)行的,否則編譯還是會報錯,因此我們需要在android塊中聲明,這個任務必須在javaCompile后強制執(zhí)行

variants是android plugin提供的操縱tasks的接口,為了讓測試工程也能編譯通過,我們還增加了在測試編譯時也刪除這些多余jar包的配置

至此,插樁的任務就完成了,測試包也可以編譯通過并通過引導程序生成代碼覆蓋率文件了,接下來需要將文件和源碼鏈接起來,即擴大生成報告使用的文件范圍到整個項目中

處理jacocoReport任務,擴展需要分析的文件的范圍

同只統(tǒng)計主工程的方法一致,我們需要對jacocoReort任務做修改,擴大sourceDirectories和classDirectories的范圍,成品如下:

task jacocoReportNew(type: JacocoReport) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."

    reports {
        xml.enabled true
        html.enabled true
    }

    def excludesFilter = ['**/R.class',
                          '**/R$*.class',
                          '**/*$ViewInjector*.*',
                          '**/BuildConfig.*',
                          '**/Manifest*.*',]

    sourceDirectories = files("src")
    classDirectories = fileTree(dir: "./build/intermediates/classes/debug", excludes: excludesFilter)
    project.rootProject.allprojects.each { project ->
        if (project.name != "shell_en_agile" && project.name != "DolphinRecordTest" && project.name != "DolphinBrowserEN"){
            sourceDirectories += files((project.projectDir).toString() + "/src")
            classDirectories += fileTree(dir:(project.projectDir).toString() + "/build/intermediates/classes/debug", excludes: excludesFilter)
        }
    }

    executionData = fileTree(dir: "/home/pgao/code-coverage/shell")
}

我們除了將主工程的相關文件加入以外,還通過遍歷根工程下所有子工程的方法增加了其他工程的相關文件。

在實際操作工程中,sourceDirectories雖然在官方文檔中給出的類型是FileCollection,按理說FileTree是他的子類,應該滿足需求,但是使用FileTree死活無法鏈接到源文件,改為Files類型后即正常了,留做后續(xù)調(diào)研吧。

另外我嘗試使用前面我們自定義的方法來遞歸獲取所有以來子工程也是持續(xù)報錯,暫時放棄這個智能的方法改用目前的手動剔除不需要的工程,同樣留給后續(xù)調(diào)研吧,這兩項都不影響我們的集成和使用

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內(nèi)容