本文為投稿文章,作者:PPPan
iOS 持續集成 - 開篇
iOS 持續集成 - 自動化 Code Review
[iOS 持續集成 - 自動化單元測試]
[iOS 持續集成 - 自動化打包與分發]
為了保證代碼質量,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) 的一層封裝,使我們不必對抽象語法樹進行解析,只需要專注規則相關的邏輯開發即可。從其提供的接口中我們可以很明顯地看出這一點。
// 遇到一元操作符 bool VisitUnaryOperator(UnaryOperator *node) // 遇到二元操作符 bool VisitBinaryOperator(BinaryOperator *node) // 遇到 Objective-C 的函數聲明 bool VisitObjCMethodDecl(ObjCMethodDecl *node)
在開發好相關的規則後,打包成 dylib,就可以在分析的時候加載我們自己的 Rule 了。
compile_commands.json
compile_commands.json 是 Clang 定義的一個規范,裡面存放了一組工作目錄、目標文件、需要被執行的命令,幫助相關工具可以獨立於編譯系統來將源代碼文件轉換為 AST 並做對應的事。
看文件內容會更直觀一些:
[ { "directory": "/path/to/project/", "command": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x ...", "file": "/path/to/project/XXXViewController.m"}, ... ]
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 上。
附一個我們團隊的配置腳本供參考:
source ~/.bash_profile cd ${SRCROOT} xcodebuild clean xcodebuild | tee xcodebuild.log oclint-xcodebuild oclint-json-compilation-database \ -e Vendor \ -e Pods \ -- \ -max-priority-1 100000 \ -max-priority-2 100000 \ -max-priority-3 100000 \ -report-type xcode \ -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,內容如下:
其中 binary operator space (HT_iOS_Coding_style 2.8) 是我們定義的錯誤rule name。在 SonarQube 上,也必須對應有這麼一條 rule 的 name,才能正確識別這個錯誤。
此時我們只需要在上述插件的 rules.txt 中添加一段
在上述插件的 profile-oclint.xml 中添加另外一段代碼
然後將這個插件打包並安裝到 SonarQube 上,SonarQube 就可以正確識別我們的問題並分類了。
使用遠端 Review
在使用前,一定要確保你的 macOS 服務器已經安裝好了最新版的 Xocde、OCLint、Jenkins、sonnar-runner,安裝好 Jenkins 的相關插件,並將自定義的 Rule 放置在服務器上(如果有的話)。
檢查並生成報告
在 Jenkins 上新建工程並配置好Git、構建觸發器等其他內容。在構建步驟中添加一步 Execute Shell ,填入下述腳本
cd YourProjectDir xcodebuild clean xcodebuild -workspace MyProject.xcworkspace -scheme HTMarket -sdk iphonesimulator | tee xcodebuild.log | xcpretty oclint-xcodebuild oclint-json-compilation-database -e Pods \ -v \ -- \ -max-priority-1 100000 \ -max-priority-2 100000 \ -max-priority-3 100000 \ -report-type pmd \ -R /path/to/diy-rules \ -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 相關的插件。)
sonar.projectKey=YOUR_PROJECT_NAME sonar.projectName=YOUR_PROJECT_NAME sonar.projectVersion=1.0 sonar.language=objc sonar.projectDescription=YOUR_PROJECT_DESCRIPTION # Path to source directories sonar.sources=/path/to/source/directories # Xcode project configuration (.xcodeproj or .xcworkspace) # -> If you have a project: configure only sonar.objectivec.project # -> If you have a workspace: configure sonar.objectivec.workspace and sonar.objectivec.project # and use the later to specify which project(s) to include in the analysis (comma separated list) sonar.objectivec.project=YOUR_PROJECT_NAME.xcodeproj sonar.objectivec.workspace= YOUR_PROJECT_NAME.xcworkspace # Scheme to build your application sonar.objectivec.appScheme=YOUR_PROJECT_NAME sonar.sourceEncoding=UTF-8 # OCLint report generated by run-sonar.sh is stored in sonar-reports/oclint.xml # Change it only if you generate the file on your own 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 命令在構建前修改相關配置。
參考鏈接
OCLint
Jenkins
SonarQube
Edsger W. Dijkstra
AST (Abstract Syntax Tree)
json compilation database format specification
xcpretty
PMD
xcode8 和 ios10 升級之後的問題集中討論帖 -- TesterHome