iOS 静态代码分析(SonarQube + Objective-C、Swift)

前言

    首次接触代码分析,源于公司对迭代规范流程的的试点。在迭代中,如何保证输出的代码是高质量的,或者说是没有明显缺陷的,这种高质如何量化,这就是接下来要介绍的: 静态代码分析。

代码分析的意义

降低风险

    迭代中代码扫描分析,成为上线发版不可或缺的一环,代码分析能检测出代码中明显存在的问题,降低风险,比如:强制类型转换、内存泄漏、空指针……

提高代码质量

    代码的质量水平,是直接影响系统稳定程度的原因之一,Code Review分为工具、人工两种,通过Code Review,可以量化代码质量,是否达到发版的质量标准,这里重点讲工具Code Review。

扫描环境搭建

1. 环境

  • 版本控制工具:Git
  • 源码语言版本:Swift 5、OC
  • 开发工具:Xcode 11.3
  • JDK:Jdk-1.8.0_221 JDK1.8地址
  • 持续集成工具:Jenkins
  • 质量管理工具:SonarQube(7.8 )

2. SonarQube的工具链

  • 源码工程(Project):Swift、OC
  • MySQL数据库(SonarQube Database):版本5.7.19-macos10.12-x86_64,存放配置信息和分析结果信息
    创建用户及数据库,mysql修改密码自行操作,随意12345678
cd /usr/local/mysql/
source ~/.bash_profile
mysql -u root -p
mysql> CREATE DATABASE sonar CHARACTER SET utf8 COLLATE utf8_general_ci;
mysql> CREATE USER 'sonar' IDENTIFIED BY 'sonar';
mysql> GRANT ALL ON sonar.* TO 'sonar'@'%' IDENTIFIED BY 'sonar';
mysql> GRANT ALL ON sonar.* TO 'sonar'@'localhost' IDENTIFIED BY 'sonar';
mysql> FLUSH PRIVILEGES;
mysql> quit;
  • 服务器(SonarQube Server):iMac ,发布应用,在线浏览、配置分析;
  • 客户端(SonarQube Scanner):版本4.2.0.1873,执行源代码分析。
注:Sonarqube-7.8 社区版 7.9版本以下支持MySQL(>=5.6<8)

3. 运行SonarQube

  • 解压sonarqube-7.8.zip
  • 修改sonarqube-7.8中conf文件夹下的sonar.properties
sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false
# mysql中创建的sonarQube用户
sonar.jdbc.username=root
# 创建用户对应的密码
sonar.jdbc.password=12345678
# 设置编码格式为UTF-8
sonar.sorceEncoding=UTF-8
# sonar登陆用户名
sonar.login=admin
# sonar登陆密码
sonar.password=admin
sonar安装成功
image.png
  • 开启SonarQube访问外部访问,修改sonarqube-7.8中conf文件夹下的sonar.properties文件,注释掉sonar.web.context=
1590831762831_E450B976-90EE-4ED1-9345-F9AF77C1BCE5.png
  • 开启apache httpd随机启动,提供外部访问
sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist
  • Mac系统升级之后,启动Sonar服务部不会自送启动MySQL,导致Sonar服务启动失败,需要在开机启动项中增加MySQL启动配置。
sh /usr/local/mysql-5.7.19-macos10.12-x86_64/support-files/mysql.server start
  • 配置sonar开机自启,在/Users/xxx/Document下创建Config文件夹,在Config下创建startup.sh文件,添加以下脚本
# MySQL开机启动
sh /usr/local/mysql-5.7.19-macos10.12-x86_64/support-files/mysql.server start

# Sonar开机启动
sh /usr/local/sonarqube-7.8/bin/macosx-universal-64/sonar.sh start

# Jenkins开机自启 并做外网端口映射
java -jar /usr/local/Cellar/jenkins/2.271/libexec/jenkins.war --httpPort=8080
  • 将run sonar.sh文件添加到iMac登录项中


    image.png

自此SonarQube服务开机自启动、外部访问配置完毕


image.png

4. 安装插件

5. 插件配置

  • 从已下载的sonar-swift-0.4.6中,拷贝backelite-sonar-swift-plugin-0.4.6.jar文件到sonarqube-7.8/extensions/plugins里

  • 从已下载的汉化插件sonar-l10n-zh-plugin-1.28中,拷贝sonar-l10n-zh-plugin-1.28文件到sonarqube-7.8/extensions/plugins里

  • 下载run-sonar-swift.sh文夹拷贝到swift项目的根目录下(与projext.xcodeproj 同级)

  • 下载sonar-project.properties文件拷贝到swift项目的根目录下(与projext.xcodeproj 同级)

image.png
  • 编辑sonar-project.properties文件,根据swift项目修改其中的参数
#
# Swift SonarQube Plugin - Enables analysis of Swift and Objective-C projects into SonarQube.
# Copyright © 2015 Backelite (${email})
#

##########################
# Required configuration #
##########################

# 项目key, 与sonar中项目key一致
sonar.projectKey=xxx

# 项目 name
sonar.projectName=xxx

# 版本号
sonar.projectVersion=2.3.0

# 分支
sonar.branch.name=XXX

# Encoding of the source code
sonar.sourceEncoding=UTF-8

# 语言 ObjC / Swift
sonar.language=swift

# swift 源文件路径
sonar.sources=Project/Clasess

# 过滤文件、目录
sonar.test.inclusions=**/*Test*/**
sonar.test.inclusions=*.swift
sonar.exclusions=Project/Clasess/Vendors/**/*

# 项目描述
sonar.projectDescription=XXX ios with the SonarScanner

# 测试目录的路径
sonar.tests=ProjectTests,ProjectUITests

# 模拟器配置
sonar.swift.simulator=platform=iOS Simulator,name=iPhone X,OS=latest

# scheme Configuration Debug/Release
sonar.swift.appConfiguration=Debug

# Xcode 工程文件配置 xcodeproj、xcworkspace
sonar.swift.project=Project.xcodeproj
sonar.swift.workspace=Project.xcworkspace

# Xcode Scheme
sonar.swift.appScheme=Project

# App 名称
sonar.swift.appName=Project

##########################
# Optional configuration #
##########################

# SCM
sonar.scm.enabled=true
# sonar.scm.url=scm:git:http://xxx

# JUnit report generated
sonar.junit.reportsPath=sonar-reports/

# Lizard report generated
sonar.swift.lizard.report=sonar-reports/lizard-report.xml

# Coverage report generated
sonar.swift.coverage.reportPattern=sonar-reports/coverage-swift*.xml

# OCLint report generated
sonar.objectivec.oclint.report=sonar-reports/*oclint.txt

# Swiftlint report generated
sonar.swift.swiftlint.report=sonar-reports/*swiftlint.txt

# Paths to exclude from coverage report (surefire, 3rd party libraries etc.)
# sonar.swift.excludedPathsFromCoverage=pattern1,pattern2
sonar.swift.excludedPathsFromCoverage=.*Tests.*

# Tailor report generated
sonar.swift.tailor.config=--no-color --max-line-length=100 --max-file-length=500 --max-name-length=40 --max-name-length=40 --min-name-length=4
sonar.swift.tailor.report=sonar-reports/*tailor.txt

# login info
sonar.host.url=http://localhost:9000
sonar.login=admin
sonar.password=xxx

# database info
sonar.jdbc.username=root
sonar.jdbc.password=xxx
sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false

   

  • run-sonar-swift.shsonar-project.properties如果不想以手动的形式放入工程目录下,可将run-sonar-swift.sh放在服务器某个目录下,将sonar-project.properties配置写入Jenkins构建脚本中,在执行Jenkins构建时通过脚本将run-sonar-swift.shcopy到工程根目录下,将sonar-project.properties写入工程根目录下。这样即使多个项目,也不用单独修改项目工程,仅修改脚本即可。扫描OC项目的Jenkins脚本如下:
source /Users/melo/.bash_profile
export LANG="en_US.UTF-8"  
export LANGUAGE="en_US.UTF-8"  
export LC_ALL="en_US.UTF-8"  

cd $WORKSPACE
  
pod install --verbose --no-repo-update 

rm -f run-sonar.sh
rm -f sonar-project.properties

cat>sonar-project.properties<<EOF
##########################
# Required configuration #
##########################

# sonar平台中相对应项目的key
sonar.projectKey=XXX

# sonar平台中相对应项目的名字
sonar.projectName=XXX

# 版本号
sonar.projectVersion=6.0.0

# Encoding of the source code
sonar.sourceEncoding=UTF-8

# 语言 ObjC / Swift
sonar.language=ObjC

# sonar检测的源文件目录,‘.’表示当前根目录下的所有文件目录;包含主要源文件的目录的逗号分隔路径
sonar.sources=.

# 检测中的测试源文件(指定单元测试文件)
# sonar.test.inclusions=**/*Test*/**
# sonar.test.inclusions=*.swift,*.m

# 检测中排除的源文件(排除的源文件不参与检测,一般排除单元测试文件、配置文件等)
# sonar.exclusions=XXX1,XXX2,XXX3……

# 项目描述
sonar.projectDescription=XXX iOS with the SonarScanner

# sonar检测的测试文件目录,‘.’表示当前根目录下的所有文件目录;包含测试源文件的目录的逗号分隔路径。从构建系统中读取Maven,Gradle,MSBuild项目。否则默认为空。
# sonar.tests=XXXTests,XXXUITests

# 检测中排除的测试源文件(排除的源文件不参与检测)
# sonar.test.exclusions=

# 模拟器配置
sonar.objectivec.simulator=platform=iOS Simulator,name=iPhone 11 Pro Max,OS=latest

# scheme Configuration Debug/Release
sonar.objectivec.appConfiguration=Debug

# Xcode 工程文件配置 xcodeproj、xcworkspace
sonar.objectivec.project=XXX.xcodeproj
sonar.objectivec.workspace=XXX.xcworkspace

# Xcode Scheme
sonar.objectivec.appScheme=TuyaSmartPublic

# App 名称
sonar.objectivec.appName=XXX

##########################
# Optional configuration #
##########################

# SCM
sonar.scm.enabled=true
# sonar.scm.url=scm:git:http://xxx

# JUnit report generated
sonar.junit.reportsPath=sonar-reports/

# Lizard report generated
sonar.objectivec.lizard.report=sonar-reports/lizard-report.xml

# Coverage report generated
sonar.objectivec.coverage.reportPattern=sonar-reports/coverage-objectivec*.xml

# 报告的名字
sonar.objectivec.oclint.report=oclint.xml

# 报告的路径
sonar.objectivec.oclint.reportPath=sonar-reports/oclint.xml

# Swiftlint report generated
#sonar.swift.swiftlint.report=sonar-reports/*swiftlint.txt

# Paths to exclude from coverage report (surefire, 3rd party libraries etc.)
# sonar.swift.excludedPathsFromCoverage=pattern1,pattern2
sonar.objectivec.excludedPathsFromCoverage=.*Tests.*

# Tailor report generated
sonar.objectivec.tailor.config=--no-color --max-line-length=100 --max-file-length=500 --max-name-length=40 --max-name-length=40 --min-name-length=4
sonar.objectivec.tailor.report=sonar-reports/*tailor.txt

sonar.objectivec.coverageType=legacy

# login info
sonar.host.url=http://localhost:9000
sonar.login=XXX
sonar.password=XXX

# database info
sonar.jdbc.username=root
sonar.jdbc.password=12345678
sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance&useSSL=false
sonar.verbose=true 
EOF

cp /Users/XXX/Documents/Sonar/run-sonar.sh $WORKSPACE
./run-sonar.sh -v
  • 配置插件规则,在扫描文件下添加.swiftlint.yml文件,根据自身代码的情况,选择性的忽略部分规则,或者修改swift插件规则。
image.png
use_nested_configs: true

# 执行 linting 时忽略的路径。 优先级比 `included` 更高。
excluded:
  - Pods

# 执行时排除掉的规则
disabled_rules:
  - force_cast # 强制转换
  - force_try # try语句判断
  - line_length # 单行代码长度

 #代码复杂度
cyclomatic_complexity:
  warning: 19
  error: 20

# 文件长度
file_length:
  warning: 999
  error: 1000

# 实体类长度
type_body_length:
  warning: 999
  error: 1000
  
# 函数体长度
function_body_length:
  warning: 99
  error: 100
  
# 命名规则可以设置最小长度和最大程度的警告/错误。此外它们也可以设置排除在外的名字
type_name:
  min_length: 4 # 只是警告
  max_length: # 警告和错误
    warning: 40
    error: 50
#  excluded: iPhone # 排除某个名字
#variable_name:
#  min_length: # 只有最小长度
#    error: 4 # 只有错误
#  excluded: # 排除某些名字
#    - id
#    - URL
#    - GlobalAPIKey
reporter: "xcode" # 报告类型 (xcode, json, csv, checkstyle)

   
更多规则,可参考

注:.swiftlint.yml 文件必须放在扫描的具体路径下,放在工程根目录下无效。

6. 配置变量配置

open ~/.bash_profile

# jdk
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/ Contents/Home
export CLASSPATH=.:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/ tools.jar
#export PATH=${JAVA_HOME}/bin:$PATH

# maven
export M3_HOME=/usr/local/Cellar/maven/3.6.3_1/libexec
export PATH=${JAVA_HOME}/bin:$M3_HOME/bin:$PATH

# gradle 
export GRADLE_HOME=/usr/local/Cellar/gradle/4.1
export PATH=$PATH:$GRADLE_HOME/bin

# mysql
export PATH=${PATH}:/usr/local/mysql/bin
export PATH=$PATH:/usr/local/mysql/support-files

# postgresql
export PATH=${PATH}:/usr/local/Cellar/postgresql/13.1/bin 

# sonarqube
export SONARQUBE=/usr/local/sonarqube-7.8 
export PATH=$SONARQUBE/bin:$PATH

# sonar-scanner
export SONAR_SCANNER=/usr/local/sonar-scanner-4.5.0.2216-macosx 
export PATH=$SONAR_SCANNER/bin:$PATH

# python
PYTHON=/Library/Frameworks/Python.framework/Versions/3.8 
export PATH=$PYTHON/bin:$PATH
alias python="/Library/Frameworks/Python.framework/Versions/3.8/bin/ python3.8"

#PYTHON=/System/Library/Frameworks/Python.framework/Versions/ 2.7
#export PATH=$PYTHON/bin:$PATH
#alias python="/System/Library/Frameworks/Python.framework/Versions/ 2.7/bin/python2.7"

# oclint 
export OCLINT=/usr/local/Cellar/oclint/0.13.1 
export PATH=$OCLINT_HOME/bin:$PATH

# swiftlint
export SWIFTLINT=/usr/local/Cellar/swiftlint/0.41.0
export PATH=$SWIFTLINT/bin:$PATH

# xcpretty
XCPRETTY=/usr/local/bin/xcpretty
export PATH=$XCPRETTY:$PATH

# tailor
export TAILOR=/usr/local/Cellar/tailor/0.12.0_1
export PATH=$TAILOR/bin:$PATH

# lizard
LIZARD=/usr/local/bin/lizard
export PATH=$LIZARD:$PATH

# slather
SLATHER=/usr/local/bin/slather
export PATH=$SLATHER:$PATH

# xctool
export XCTOOL=/usr/local/Cellar/xctool/0.3.7
export PATH=$XCTOOL/bin:$PATH

# gcovr
export GCOVR=/usr/local/Cellar/gcovr/4.2_1
export PATH=$GCOVR/bin:$PATH

# xcodebuild
XCODEBUILD=/usr/bin/xcodebuild
export PATH=$XCODEBUILD:$PATH

# local
export USRLOCAL=/usr/local
export PATH=$USRLOCAL/bin:$PATH

保存环境配置
source ~/.bashrc

7. 源码工程配置

Tagrget配置:Tests、UITests必不可少

image.png

8. Jenkins构建扫描

创建Jenkins扫描任务
image.png
配置源码
image.png

    关于SonrQube 分析iOS项目的详细配置,可参考SonarQube Integration with iOS

配置Jenkins扫描脚本
source /Users/xxxx/.bash_profile
export LC_ALL="en_US.UTF-8"
cd $WORKSPACE
./run-sonar-swift.sh -v
扫描成功结果图
image.png
扫描日志
image.png
SonarQube分析结果
image.png
image.png
导出报告

社区版导出报告需要安装开源插件sonar-cnes-report.
这里因为部署的是sonar7.8的版本,与sonar-cnes-report最新的4.0.0版本不兼容,新增插件后导致sonar重启失败。选择一个较低的版本即可,这里选择3.3.1版本即可,其他版本也可以尝试一下。
将下载的 jar包复制到中sonarqube的plugins目录下,重新启动 sonarqube,然后单击“更多”>“CNES 报告”。
注意:由于个人安装sonarqube的目录各不相同,这里plugins目录为:/usr/local/sonarqube-7.8/extensions/plugins,选择自己的目录即可。

image.png

image.png

image.png

常见错误:
1、Server接受的数据包大小,会受 max_allowed_packet 参数限制,导致写入或者更新失败。

11:44:53.491 ERROR: Error during SonarScanner execution
Failed to upload report - An error has occurred. Please contact your administrator
+ returnValue=2
+ set +x
ERROR - Command 'sonar-scanner -X -Dsonar.verbose=true' failed with error code: 2

解决方案:修改MySQL的max_allowed_packet的大小。
关于max_allowed_packet的修改,请参考:MYSQL 5.7 无法修改max_allowed_packet参数?

    到这里,SonarQube代码扫描结束,接下来就是根据分析结果,做针对性的整改代码。我们目前的策略把定义为阻断、严重、主要问题清零,次要、提示这两类问题先不处理,后期有时间再处理。中间针对一些不合理的维度,比如:循环复杂度到达10定义为严重问题,文件、类代码行数限制200行,超过定义主要问题,单行代码长度限制120字符等等,做了插件规则的调整:忽略/修改阈值。总之一句话:具体问题具体分析,根据自家代码实际情况做评估,如果代码整改需要大刀阔斧,那跟重构无异了,厉害关系需要权衡清楚。另外SonarQube问题的整改,尽量放在项目中去做,迭代周期太短,开发、测试很难把控到位。如果一定要放在迭代中来做,那一定要排好整改计划,按节奏推进。

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

推荐阅读更多精彩内容