iOS 持續整合系列 - 自動化 Code Review

ZY_FlyWay發表於2018-10-24

iOS 持續整合系列 - 自動化 Code Review

為了保證程式碼質量,Code Review 是非常重要的一環。細到*的位置是否正確,大到程式碼的結構是否符合了軟體開發的一些基本原則,都在這項工作的範圍內。

受限於現實情況,大多數團隊沒有足夠的時間進行 Code Review,那麼只能把一部分 CR 工作交給計算機去完成了。我們只需要定下合理的流程,用程式碼告訴計算機需要做什麼,剩下的就交給我們可靠的夥伴吧。

應用了自動化 Code Review 後,如果你的程式碼寫得不好,Xcode 會表示不開心。

如果你忽略 Xcode 的心情,那麼質量管理平臺會默默地記錄這一切。

這套東西既幫助開發們寫出更高質量的的程式碼,也給經理們對工程質量的評估提供了一個切面的支援,同時只需要花費較少的人力維護,聽起來是不是躍躍欲試了呢 : )

流程

整體的工作流程非常簡單,如圖:

自動化 Code Review 總體流程

關鍵點在於本地 Review和遠端 Review這兩步。前者是提供給開發者一個即時的程式碼質量反饋,以便開發者修改,從而避免在接下來的遠端 Review 中得到一個較低的得分。後者則是為了生成相關報表,為專案管理人員跟蹤專案質量提供依據。在很多大公司裡,這也是開發者們績效的參考之一。

剩下的就是一些膠水步驟了,如何讓過程更自動化,就是膠水步驟要做的事。例如利用 WebHook 自動觸發遠端 Review,利用 Git 的鉤子進行增量校驗而不是全量校驗等。這些我們放在後面聊,先來看看本地校驗的流程。

本地 Review

本地自動化 Code Review

在本地 Review 環節,開發者只需要像往常一樣按下 CMD + B,然後只要靜靜地等待進度條讀完,滿屏的⚠️就會精確地指示出某一行的程式碼違反了哪條規則。此時開發者就可以根據程式碼規範進行對應修改。

從按下按鍵到產生警告主要發生了這麼幾件事情:

  • 生成 compile_commands.json 檔案
  • OCLint 讀取相關的 Rules,逐個掃描 compile_commands.json 中的 .m 檔案
  • OCLint 將生成的報告展示在 Xcode 上

實現本地 Review 的核心就是 OCLint 和 compile_commands.json檔案

OCLint

 

OCLint 是一個開源的,基於 Clang 用 C++ 編寫而成的,可以用於 C、C++ 和 Objective-C 的靜態程式碼分析器。它可以在掃描的過程中動態載入規則檔案(Rules),因此可以實現非常靈活的,高度可自定義的程式碼分析方案。它幾乎可以和大多數系統無縫整合,例如 Cmake、Bear、xcodebuild、xctool、Xcode、xcpretty、Jenkins CI、Travis CI 等。你可以在這裡找到如何將其和 Xcode 配合使用。

最新版本的 OCLint 已經自帶了 71 條 Rules,基本上都是先人寶貴的經驗,比如這條禁用 goto 語句的 Rule,就是來源於 Edsger W. Dijkstra 1968 年的一篇手稿

這 71 條 Rules 已經可以幫助我們避免一部分因書寫習慣和語言誤區而導致的問題,但是對於有完整編碼規範的公司來說顯然是不夠的。我們必須要自己開發 Rules。

幸運的是,OCLint 已經為我們準備好了一切。

OCLint 提供了 Clang 和 AST (Abstract Syntax Tree) 的一層封裝,使我們不必對抽象語法樹進行解析,只需要專注規則相關的邏輯開發即可。從其提供的介面中我們可以很明顯地看出這一點。


 
  1. // 遇到一元操作符

  2. bool VisitUnaryOperator(UnaryOperator *node)

  3.  
  4. // 遇到二元操作符

  5. bool VisitBinaryOperator(BinaryOperator *node)

  6.  
  7. // 遇到 Objective-C 的函式宣告

  8. bool VisitObjCMethodDecl(ObjCMethodDecl *node)

  9.  

在開發好相關的規則後,打包成 dylib,就可以在分析的時候載入我們自己的 Rule 了

compile_commands.json

compile_commands.json 是 Clang 定義的一個規範,裡面存放了一組工作目錄、目標檔案、需要被執行的命令,幫助相關工具可以獨立於編譯系統來將原始碼檔案轉換為 AST 並做對應的事。

看檔案內容會更直觀一些:


 
  1. [

  2. {

  3. "directory": "/path/to/project/",

  4. "command": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x ...",

  5. "file": "/path/to/project/XXXViewController.m"

  6. },

  7. ...

  8. ]

OCLint 可以根據 compile_commands.json 中的內容,批量檢查原始碼檔案。

xcpretty

還有一個點需要關注的是,如何生成 compile_commands.json 檔案?

最便捷的方式是使用 oclint-xcodebuild 來生成。首先,利用xcodebuild 生成 xcodebuild.log 檔案。

xcodebuild | tee xcodebuild.log  

然後利用 oclint-xcodebuild 生成 compile_commands.json

oclint-xcodebuild  

截至 Xcode 8.1,這種做法可以正確生成 json 檔案。由於 OCLint 團隊已經聲稱不再維護 oclint-xcodebuild , 因此可能在未來的某個 Xcode 版本中這個方法將不再適用。

另一個推薦的方法是利用 xcpretty 。

xcpretty 可以一句話生成 json 檔案。

xcodebuild | xcpretty -r json-compilation-database --output /path/to/compile_commands.json  

使用本地 Review

瞭解了這些工具後就很容易明白本地自動化 Code Review 是如何工作的,使用方式也非常容易理解了:

  1. 首先在電腦本地安裝好 OCLint 並拿到公司自定義的 Rules 檔案
  2. 在 Xcode 上配置好工程
  3. build 工程,等待結果顯示在 Xcode 上。

附一個我們團隊的配置指令碼供參考:


 
  1. source ~/.bash_profile

  2. cd ${SRCROOT}

  3. xcodebuild clean

  4. xcodebuild | tee xcodebuild.log

  5. oclint-xcodebuild

  6. oclint-json-compilation-database \

  7. -e Vendor \

  8. -e Pods \

  9. -- \

  10. -max-priority-1 100000 \

  11. -max-priority-2 100000 \

  12. -max-priority-3 100000 \

  13. -report-type xcode \

  14. -R /path/to/rules

遠端 Review

遠端自動化 Code Review

遠端 Review 和 本地 Review 大體相似,區別在與引用構建的指令碼的物件從 Xcode 變成了 Jenkins CI ,報告的展示者從 Xcode 變成了 SonarQube 。其流程是這樣的:

工程師通過 git push 提交程式碼 → Web Hook 觸發 Jenkins 構建 → OCLint 掃描程式碼生成PMD格式報告 → Sonar-runner 讀取報告並展現到 SonarQube。

CI 環境

為了實現遠端 Review ,服務端必須首先有一套 CI 環境。鑑於 iOS 的特殊性,伺服器必須是 macOS 系統。CI 我們直接選擇開源的 Jenkins,質量管理平臺則選用開源的 SonarQube。Jenkins 大名鼎鼎大家都非常熟悉了,SonarQube 則相對少的人瞭解。

SonarQube 是一個質量管理平臺,在 SonarQube 上,你可以看到一個專案的程式碼行數、檔案數量、程式碼重複率、違反的程式碼規範、技術債時間等等指標。SonarQube 對 Java 的支援極度友好,提供了 SonarScanner 可以直接對 Java 原始碼進行掃描。Objective-C 就沒有這麼幸運了。雖然 SonarQube 也提供了 Objective-C 的報告展示的支援,但靜態分析還是得依靠 OCLint 。

Sonnar-Runner

我們在 Jenkins 上執行 OCLint 生成了報告。需要一箇中間人將報告解析成 SonarQube 可以理解的格式並傳輸到 SonarQube 平臺。這個中間人就是 Sonnar-Runner。Sonnar-Runner 在我們的系統中也僅僅扮演這個搬運工的角色。你可以從這裡瞭解到如何在 Jenkins 上安裝和使用 Sonnar-Runner。

Sonnar-Runner 只能解析 PMD 格式的報告,因此我們在使用 OCLint 分析程式碼後,需要將報告格式輸出為 PMD 格式

oclint -report-type pmd -o ./report.xml  

Rules in Sonar

SonarQube 有一套規則,將程式碼問題按照嚴重程度分為 5 個等級,不同等級的問題會以不同權重影響到專案質量評分。這套規則和 OCLint 生成的報告中的 Rule name 必須要一一對應,SonarQube 才能正確將報告中的問題歸類並評分。

如果你使用 OCLint 原生的 Rules 來檢查程式碼,只需要在 SonarQube 上安裝 SonarQube Plugin for Objective C 外掛,相關的報告就會被正確識別了。

如果是使用了自行開發的 Rules ,只需要 Clone 上述外掛,並在profile-oclint.xml 和 rules.txt 中新增相關的 rule name ,然後打包並將這個外掛安裝到 SonarQube 上即可。

舉個例子:

當我們用自行開發的 Rule 檢查完程式碼後,生成了report.xml,內容如下:


 
  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <pmd version="oclint-0.11">

  3. <file name="/path/to/TerribleCode.m">

  4. <violation rule="binary operator space (HT_iOS_Coding_style 2.8)" begincolumn="9" endcolumn="157" beginline="73" endline="73" priority="3" ruleset="HT_iOS_rules" >

  5. 多元運算子和他們的運算元之間至少需要一個空格

  6. </violation>

  7. </file>

  8. </pmd>

其中 binary operator space (HT_iOS_Coding_style 2.8) 是我們定義的錯誤rule name。在 SonarQube 上,也必須對應有這麼一條 rule 的 name,才能正確識別這個錯誤。

此時我們只需要在上述外掛的 rules.txt 中新增一段


 
  1. binary operator space (HT_iOS_Coding_style 2.8)

  2. ----------

  3.  
  4. Summary:多元運算子和他們的運算元之間至少需要一個空格。

  5.  
  6. Severity: 2

  7. Category: Hengtian iOS Coding Standard

  8.  

在上述外掛的 profile-oclint.xml 中新增另外一段程式碼


 
  1. <rule>

  2. <repositoryKey>OCLint</repositoryKey>

  3. <key>binary operator space (HT_iOS_Coding_style 2.8)</key>

  4. </rule>

然後將這個外掛打包並安裝到 SonarQube 上,SonarQube 就可以正確識別我們的問題並分類了。

使用遠端 Review

在使用前,一定要確保你的 macOS 伺服器已經安裝好了最新版的 Xocde、OCLint、Jenkins、sonnar-runner,安裝好 Jenkins 的相關外掛,並將自定義的 Rule 放置在伺服器上(如果有的話)。

檢查並生成報告

在 Jenkins 上新建工程並配置好Git、構建觸發器等其他內容。在構建步驟中新增一步 Execute Shell ,填入下述指令碼


 
  1. cd YourProjectDir

  2. xcodebuild clean

  3. xcodebuild -workspace MyProject.xcworkspace -scheme HTMarket -sdk iphonesimulator | tee xcodebuild.log | xcpretty

  4. oclint-xcodebuild

  5. oclint-json-compilation-database -e Pods \

  6. -v \

  7. -- \

  8. -max-priority-1 100000 \

  9. -max-priority-2 100000 \

  10. -max-priority-3 100000 \

  11. -report-type pmd \

  12. -R /path/to/diy-rules \

  13. -o /path/to/report.xml

指令碼大致和本地 Review 一致,有三個地方需要注意一下。

  1. xcodebuild 命令新增了 -sdk iphonesimulator引數,以避免 build 需要 Code Sign 的問題。
  2. -report-type pmd 輸出格式必須為 pmd 格式
  3. -o /path/to/report.xml 注意輸出報告的路徑,下一步sonnar-runner 讀取時會用到。

讀取到 SonarQube

在上一步的下方再新增一步 Invoke Standalone SonarQube Analysis,選擇好你的 sonnar-runner。並在 Analysis Properties 中新增如下配置:(如果沒有這一項,你可能需要安裝 SonarQube 相關的外掛。)


 
  1. sonar.projectKey=YOUR_PROJECT_NAME

  2. sonar.projectName=YOUR_PROJECT_NAME

  3. sonar.projectVersion=1.0

  4. sonar.language=objc

  5. sonar.projectDescription=YOUR_PROJECT_DESCRIPTION

  6.  
  7. # Path to source directories

  8. sonar.sources=/path/to/source/directories

  9.  
  10. # Xcode project configuration (.xcodeproj or .xcworkspace)

  11. # -> If you have a project: configure only sonar.objectivec.project

  12. # -> If you have a workspace: configure sonar.objectivec.workspace and sonar.objectivec.project

  13. # and use the later to specify which project(s) to include in the analysis (comma separated list)

  14. sonar.objectivec.project=YOUR_PROJECT_NAME.xcodeproj

  15. sonar.objectivec.workspace= YOUR_PROJECT_NAME.xcworkspace

  16.  
  17. # Scheme to build your application

  18. sonar.objectivec.appScheme=YOUR_PROJECT_NAME

  19.  
  20. sonar.sourceEncoding=UTF-8

  21.  
  22. # OCLint report generated by run-sonar.sh is stored in sonar-reports/oclint.xml

  23. # Change it only if you generate the file on your own

  24. sonar.objectivec.oclint.report=YOUR_REPORT_FILE_PATH

注意看註釋並修改 YOUR_PROJECT_NAME 、YOUR_PROJECT_DESCRIPTION、和 YOUR_REPORT_FILE_PATH為你專案的值。

一切順利的話,在 Jenkins 上立即構建,你就可以在你的 Sonar 平臺上看到程式碼質量報告了。

配合好構建觸發器 和 Git 平臺的 WebHook 功能,就可以在開發提交程式碼或者合併分支等關鍵點自動觸發構建了。

Troubleshooting

為什麼生成的 compile_commands.json 為空

檢查 log 是否為空,如果 log 為空則代表 build 失敗。排除失敗原因後即可正常生成。

Jenkins 構建遇到了如下問題

❌  Code signing is required for product type 'Application' in SDK 'iOS 10.0'

遇到這樣的情況,是因為構建了 Release 版本,且專案在 Xcode8+ 上開啟了 Automatic Code Sign。解決方法如下:

  1. 如果只需要檢查程式碼規範,則在 xcodebuild 命令後新增 -sdk iphonesimulator 引數指明以 Debug 方式構建即可。
  2. 如果希望構建 Release 版本,那麼關閉自動簽名,在 CI 系統上手動配置證照和Proversion Profile。或者保留自動簽名,參考這個回答用 sed 命令在構建前修改相關配置。

相關文章