背景
随着代码量的日益添加,以及团队的扩大,咱们往往由于需求需求尽快上线以及快速迭代,导致代码并不是很标准,时间长了就留下了一堆技能债,代码的质量也没有了确保。所以开端尝试一些代码质量相关建造,希望能够通过代码静态扫描的方法,协助咱们扫描出一些代码漏洞,然后尝试去修复漏洞和bug,以此来确保代码质量。
东西与渠道
本文触及的东西及渠道:
- xcodebuild
- xcpretty
- oclint
- infer
- sonarqube
-
- sonar-scanner
-
- sonar-server
-
- postgresql
-
- sonar-swift
开源计划介绍及装备
大部分iOS渠道代码静态剖析基本是根据开源的oclint
或许infer
进行的,本文尽管运用sonarqube
,但免费的剖析计划中心仍然是oclint
与infer
sonarqube
是一个开源的静态代码剖析渠道,供给免费的社区版,免费的社区版不支撑Objective-C
,但github
有供给开源的插件,Objective-C
,付费的社区版plus有支撑Objective-C
的剖析插件SonarCFamily for C
不管免费计划还是付费计划,首先都是根据xcodebuid过程中的日志来进行的,咱们这次首要针对开源计划来讲相关装备流程。
对于运用开源计划来说,本质流程如下:
xcodebuild
iOS中心东西,装置好Xcode
就会自带此东西,由于oclint
剖析的中心是xcodebuild
在编译app过程中的log
,所以需求xcodebuild
(build
失利也会对现已build
的日志进行剖析,但尽量确保能够build
成功)
假如项目是在workspace中需求指定-workspace和对应的scheme,能够通过xcodebuild -list检查
//履行
$ xcodebuild -list
//履行
$ xcodebuild -workspace CsdnPlus.xcworkspace -scheme CsdnPlus
咱们并不需求打出的IPA
包能够装置到手机上,只是需求build
过程中的日志罢了,所以咱们只需求打出模拟器下Debug
包就能够了。
//履行
$ xcodebuild -showsdks
由于xcodebuild
会有缓存,所以咱们每次履行前需求clean
$ xcodebuild -workspace CsdnPlus.xcworkspace -scheme CsdnPlus clean
履行build
编译
$ xcodebuild -scheme CsdnPlus -workspace CsdnPlus.xcworkspace -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 12 Pro Max' -configuration Debug
能编译成功的话就能够进入下一步了
xcpretty
xcpretty is a fast and flexible formatter for xcodebuild. It does one thing, and it should do it well.
xcpretty
是一个格局化xcodebuild
输出的东西。装置:
$ gem install xcpretty
-r, --report
指定生成的陈述格局可选为junit, html, json-compilation-database
。
-o, --output
指定生成的文件称号。
这儿咱们运用json-compilation-database
格局,输出文件名为compile_commands.json
(留意输出称号不能更改,否则后边oclint会报错,由于oclint源码中内置了校验称号,详细可检查源码)
用法:
紧跟在xcodebuild
相关句子后边,比方:
$ xcodebuild [flags] | xcpretty
能够结合tee
进行日志收集
$ xcodebuild [flags] | tee xcodebuild.log | xcpretty
履行完好指令生成编译数据compile_commands.json
文件:
首先需求用
xcodebuild
clean
和build
项目,而且添加COMPILER_INDEX_STORE_ENABLE=N
O参数,否则可能会出现报错:oclint: error: one compiler command contains multiple jobs
报错
$ xcodebuild -scheme CsdnPlus -workspace CsdnPlus.xcworkspace -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 12 Pro Max' -configuration Debug GCC_PRECOMPILE_PREFIX_HEADER=YES CLANG_ENABLE_MODULE_DEBUGGING=NO COMPILER_INDEX_STORE_ENABLE=NO OTHER_CFLAGS="-DNS_FORMAT_ARGUMENT(A)= -D_Nullable_result=_Nullable" | tee xcodebuild.log | xcpretty -r json-compilation-database -o compile_commands.json
OCLint
OCLint is a static code analysis tool for improving quality and reducing defects by inspecting C, C++ and Objective-C code
OCLint
是根据 Clang Tooling
开发的静态剖析东西,首要用来发现编译器检查不到的那些潜在的关键技能问题。是进行OC
代码剖析的中心东西,首要对上一步生成的compile_commands.json
进行剖析,生成陈述
指令装置:
$ brew tap oclint/formulae
$ brew install oclint
我主张运用装置包来装置
OCLint
,Homebrew
装置只能装置到20.11
版别,最新Xcode
版别对应的是22.02
。假如装置版别不符合,OClint
剖析出来只要一堆compiler error
。
下载装置包装置:
https://github.com/oclint/oclint/releases
装备环境变量:
export PATH="/Users/csdn/oclint-22.02/bin:$PATH"
source ~/.zshrc
在终端输入 oclint --version
,验证是否装置成功。
在终端输入oclint --help
检查指令介绍
其间咱们首要运用oclint-json-compilation-database指令,Github源码
oclint-json-compilation-database
指令支撑指定校验文件夹和过滤指定文件夹,本质上最终履行oclint -p
指令,能够通过附加-v
检查,一起还支撑运用--
后边跟上oclint
履行参数。
例如:
// 此处--符号后的参数是传递给oclint的
$ oclint-json-compilation-database -v -e Pods -e xxxx -- -report-type html -o report.html
oclint
的-rc
选项能够自定义校验的参数值,例如:
$ oclint-json-compilation-database -v -e Pods -e xxxx -- -rc LONG_METHOD=60 -rc LONG_LINE=100
别的当咱们需求自定义多个
oclint
参数时,咱们能够将装备写在.oclint文件中
disable-rules: // 不运用的规矩
- LongLine
rulePaths: // oclint校验规矩地点的途径,Mac端默许在/usr/local/lib/oclint/rules,假如不需求自定义规矩的话能够不装备此项
- /etc/rules
rule-configurations: // 自定义装备参数
- key: CYCLOMATIC_COMPLEXITY
value: 15
- key: NPATH_COMPLEXITY
value: 300
output: oclint.xml // 生成的陈述
report-type: xml // 生成的陈述格局支撑html、xml、json等
max-priority-1: 20 // 等级1的问题最大个数,假如检测出的问题超越这个个数就会自动终止
max-priority-2: 40 // 等级2的问题最大个数
max-priority-3: 60 // 等级3的问题最大个数
enable-clang-static-analyzer: false //
以下是OCLint内置支撑的72条Rule,能够通过 --list-enabled-rules x
检查
$ oclint --list-enabled-rules x
enabled rules:
- TooManyMethods
- DestructorOfVirtualClass
- DeadCode
- EmptyForStatement
- AvoidDefaultArgumentsOnVirtualMethods
- ProblematicBaseClassDestructor
- MisplacedDefaultLabel
- EmptyFinallyStatement
- CallingProhibitedMethod
- RedundantIfStatement
- CollapsibleIfStatements
- UnnecessaryElseStatement
- ConstantConditionalOperator
- DeepNestedBlock
- AssignIvarOutsideAccessors
- UnnecessaryNullCheckForDealloc
- RedundantNilCheck
- RedundantLocalVariable
- EmptyDoWhileStatement
- UnusedMethodParameter
- BitwiseOperatorInConditional
- ReturnFromFinallyBlock
- MultipleUnaryOperator
- DoubleNegative
- MissingCallToBaseMethod
- EmptyWhileStatement
- ShortVariableName
- ParameterReassignment
- UselessParentheses
- ThrowExceptionFromFinallyBlock
- UnnecessaryDefaultStatement
- HighNcssMethod
- PreferEarlyExit
- MissingBreakInSwitchStatement
- TooManyParameters
- CallingProtectedMethod
- AvoidBranchingStatementAsLastInLoop
- MissingAbstractMethodImplementation
- MissingHashMethod
- MisplacedNullCheck
- MisplacedNilCheck
- UseContainerLiteral
- LongLine
- ForLoopShouldBeWhileLoop
- HighNPathComplexity
- LongMethod
- EmptySwitchStatement
- RedundantConditionalOperator
- EmptyTryStatement
- EmptyCatchStatement
- UseObjectSubscripting
- AvoidPrivateStaticMembers
- EmptyElseBlock
- InvertedLogic
- LongClass
- LongVariableName
- GotoStatement
- BrokenOddnessCheck
- UseNumberLiteral
- TooFewBranchesInSwitchStatement
- UseBoxedExpression
- JumbledIncrementer
- EmptyIfStatement
- BranchDivergence
- MissingDefaultStatement
- HighCyclomaticComplexity
- NonCaseLabelInSwitchStatement
- ConstantIfExpression
- BrokenNullCheck
- BrokenNilCheck
- TooManyFields
- UnusedLocalVariable
假如咱们运用.oclint
最终咱们将.oclint
放在与compile_commands.json
相同的途径下,并在该途径下履行指令:
$ oclint-json-compilation-database -v -e Pods
或许直接履行指令:
$ oclint-json-compilation-database -e Pods -- -report-type pmd \
-rc=LONG_CLASS=1500 \
-rc=NESTED_BLOCK_DEPTH=5 \
-rc=LONG_VARIABLE_NAME=80 \
-rc=LONG_METHOD=200 \
-rc=LONG_LINE=300 \
-disable-rule ShortVariableName \
-disable-rule ObjCAssignIvarOutsideAccessors \
-disable-rule AssignIvarOutsideAccessors \
-allow-duplicated-violations=false\
-max-priority-1=100000 \
-max-priority-2=100000 \
-max-priority-3=100000 >> oclint.xml
最终会生成oclint.xml
(也能够自己生成html
格局,直接检查作用)
Infer
Infer
是Facebook
开源的一款代码扫描软件,能够剖析Objective-C
,Java
或许C
代码,陈述潜在的问题。任何人都能够运用Infer
检测运用,这能够将那些严峻的 bug 摧残在发布之前,一起避免运用崩溃和性能低下。
- 指令装置
$ brew install infer
在终端输入 infer --version
,验证是否装置成功。
Infer
与OCLint
一样,都是剖析compile_commands.json
文件。在compile_commands.json
文件相同途径下履行指令:
# --skip-analysis-in-path 是疏忽扫描目录
$ infer run --skip-analysis-in-path Pods --keep-going --compilation-database compile_commands.json
留意:指令中一定要有
--keep-going
否则会有过错导致无法进行剖析。 别的需求在xcodebuild
指令中添加OTHER_CFLAGS="-DNS_FORMAT_ARGUMENT(A)= -D_Nullable_result=_Nullable"
。
扫描出的成果会在工程目录下的 infer-out
文件中,其间详细的代码会以 csv,txt,json 的格局别离存在对应的文件中。
总结:
OCLint
基本上剖析都是一些代码标准的问题,Infer
能够检查出空指针拜访、资源泄露以及内存泄露。
sonarqube
sonarqube
是一个供给代码静态剖析的渠道,供给了一套完好的静态剖析计划,包括后端及前端页面,能够结合jenkins
、gitlab
等渠道来进行代码剖析。sonarqube
分社区版别和商业化版别,能扫描多种语言而且开源。官网地址。
由于其底层源码为java
开发的,所以对java
代码支撑比较完善,但是免费的社区版并不支撑OC
,所以咱们假如要凭借此渠道的话,有以下两种方法:
- 开源插件sonar-swift
,支撑
Objective-C
和Swift
/Java
,支撑导入SwiftLint
、Infer
、OCLint
、Lizard
、Fauxpas
东西的扫描剖析成果。最新v1.6
版别,兼容SonarQube 8.9LTS
版别。该插件是好未来
研发团队研发而且开源。 - 付费运用社区版plus,供给了SonarCFamily for C插件 有官方供给技能支撑,250+的rules可供选择,不可自定义规矩。
咱们将运用开源插件计划。
sonar-services(sonarqube)装置
sonarqube
装置有两种方法
- docker 装置
$ docker pull sonarqube:8.9.7-community
- 下载二进制装置包 选中 8.9.7LTS 社区版别,下载
下载完装置包后,进入bin/macosx-universal-64
目录。履行指令:
$ sh sonar.sh start
控制台输出Started SonarQube
阐明发动成功。
在浏览器拜访http://localhost:9000/
,能翻开页面阐明发动成功。
默许账号为admin,暗码为admin。
留意:假如发动失利能够去
sonarqube/logs
下检查日志。 装置时,发现sonarqube 8.9.7版别需求Java 11环境。所以需求先装置Java 11环境。 装置Java 11:
发动成功后咱们在最下面会看到warning
警告主张咱们自己装备数据库,需求阐明的是SonarQube
假如想持久化保存数据,是需求依靠数据库的。
SonarQube
默许供给H2
存储,只能暂时存储一些小项目成果,仅为了演示运用。
在conf/sonar.properties
下装备数据库地址即可。可选 MySQL
、Oracle
、PostgreSQL
。
下面咱们就来装备数据库(mysql后续将不再支撑):这儿咱们运用的是PostgreSQL
,装备参阅
PostgreSQL
用Homebrew 履行指令装置PostgreSQL:
$ brew install postgresql //装置
装置完数据库后,发动数据库,履行指令:
$ pg_ctl -D /usr/local/var/postgres start //发动
$ createdb //创立数据库
$ psql //登录控制台
数据库装置创立好后,咱们需求供给一个数据库和账号sonarqube
运用。履行指令:
CREATE USER sonarqube WITH PASSWORD 'sonarqube';//创立用户
CREATE DATABASE sonar OWNER sonarqube;//创立属于该用户的数据库
创立完结后履行指令退出:
\q
然后咱们去sonarqube/conf
目录下修改sonar.properties
,将数据库相应装备装备完结
sonar.jdbc.username=sonarqube
sonar.jdbc.password=sonarqube
sonar.jdbc.url=jdbc:postgresql://localhost/sonar
修改完结后在sonarqube/bin/macosx-universal-64/
目录下履行: sh sonar.sh restart
,此刻警告现已消除了。
至此咱们的sonarqube
的前端服务现已装备完结了。
汉化包装置
通过Github 下载对应版别汉化包jar包插件。
下载插件放到/extensions/plugins
目录下。重启sonarqube服务,就能够看到汉化后的界面。
sonar-swift
通过GitHub 下载对应插件
下载插件放到/extensions/plugins
目录下。重启sonarqube服务,就能够了。
sonar-scanner
sonar-scanner
用来扫描本地代码,而且上传到SonarQube
渠道中。
- 下载装置地址;按照不同的操作系统选择不同装置包即可。
- 装备环境变量:
$ vim ~/.bash_profile
#sonar-scanner for cli
export PATH=$PATH:/Users/csdn/scanner/bin:$PATH
$ source ~/.bash_profile
sonar-scanner
分为两种运用方法:
- 装备文件方法:
在需求扫描项目根目录下新建sonar-project.properties
文件,内容如下:
sonar.projectKey=CsdnPlus
sonar.projectName=CsdnPlus
sonar.language=objc
sonar.sources=/Users/csdn/.jenkins/workspace/csdn_build_ios/
sonar.objectivec.workspace=CsdnPlus.xcworkspace
sonar.objectivec.appScheme=CsdnPlus
sonar.sourceEncoding=UTF-8
sonar.junit.reportsPath=sonar-reports/
sonar.objectivec.oclint.report=sonar-reports/oclint.xml
sonar.swift.infer.report=infer-out/report.json
进入项目根目录下,然后输入sonar-scanner
指令,履行代码剖析。
- 指令行方法:
在指令中设置了参数:
sonar-scanner -Dsonar.projectKey=CsdnPlus -Dsonar.projectName=CsdnPlus -Dsonar.projectName=CsdnPlus -Dsonar.projectVersion=5.1.0
假如咱们要SonarQube疏忽一些指定目录或许文件的扫描,能够在装备中添加sonar.exclusions
例如:
//疏忽指定文件目录或许文件
sonar.exclusions=**/Resource/**,**/*.py
或许能够在指令中设置参数,例如:
sonar-scanner -Dsonar.exclusions=**/Resource/**,**/*.py -Dsonar.projectKey=CsdnPlus -Dsonar.projectName=CsdnPlus -Dsonar.projectName=CsdnPlus -Dsonar.projectVersion=5.1.0
这儿的中心就是在上面步骤中由OCLint
生成的oclint.xml
文件与Infer
生成的report.json
,别的留意oclint.xml
有必要放至sonar-reports
文件下。report.json
在Infer
生成的infer-out
目录下。
指令履行成功后,便可在sonarQube的前端页面看到对应的检测作用了。
检测作用图:
集成进Jenkins
咱们项目本身现已有自动化构建服务,所以比较方便。
-
Jenkins项目装备,选项中添加OCLint(能够自己命名)选项
-
构建Shell指令中,添加OCLint相关指令
if [ "$MODE"x = "OCLint"x ]
then
sh /Users/csdn/.jenkins/workspace/csdn_build_ios/fastlane/oclint.sh "$GIT_BRANCH"
fi
- 企业微信通知, Shell中添加企业微信机器人URL
//获取本机IP
local_ip=$(ifconfig | grep '\<inet\>'| grep -v '127.0.0.1' | awk '{ print $2}' | awk 'NR==1')
curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=31ef53a9-1e97-42c6-9cc7-fb4432bd41f9' \
-H 'Content-Type: application/json' \
-d '
{
"msgtype":"news",
"news":{
"articles":[
{
"title":"SonarQube 静态代码扫描完结",
"url":"http://'${local_ip}':9000/",
"description":"APP称号:CSDNAPP\n扫描代码分支:'$1'",
"picurl":"https://img-bss.csdnimg.cn/202103251639445966.png"
}
]
}
}'
一些有用的
shell
指令获取APP称号:
product_name=`sed -n '/PRODUCT_NAME/{s/PRODUCT_NAME = //;s/;//;s/^[[:space:]]*//;s/\"//g;p;q;}' ./$myscheme.xcodeproj/project.pbxproj`
获取APP版别:
version_number=`sed -n '/MARKETING_VERSION/{s/MARKETING_VERSION = //;s/;//;s/^[[:space:]]*//;s/\"//g;p;q;}' ./$myscheme.xcodeproj/project.pbxproj`
获取本机IP地址:
local_ip = $(ifconfig | grep '\<inet\>'| grep -v '127.0.0.1' | awk '{ print $2}' | awk 'NR==1')
- 附上完好
oclint.sh
指令:
#!/bin/bash
COLOR_ERR="\033[1;31m" #出错提示
COLOR_SUCC="\033[0;32m" #成功提示
COLOR_QS="\033[1;37m" #问题色彩
COLOR_AW="\033[0;37m" #答案提示
COLOR_END="\033[1;34m" #色彩结束符
# 寻觅项目的 ProjectName
function searchProjectName () {
# maxdepth 查找文件夹的深度
find . -maxdepth 1 -name "*.xcodeproj"
}
function oclintForProject () {
# 预先检测所需的装置包是否存在
if which xcodebuild 2>/dev/null; then
echo 'xcodebuild exist'
else
echo 'xcodebuild 未装置,请装置Xcode'
fi
if which oclint 2>/dev/null; then
echo 'oclint exist'
else
echo 'oclint 未装置,请装置OCLint'
fi
if which xcpretty 2>/dev/null; then
echo 'xcpretty exist'
else
gem install xcpretty
fi
# 指定编码
export LANG="zh_CN.UTF-8"
export LC_COLLATE="zh_CN.UTF-8"
export LC_CTYPE="zh_CN.UTF-8"
export LC_MESSAGES="zh_CN.UTF-8"
export LC_MONETARY="zh_CN.UTF-8"
export LC_NUMERIC="zh_CN.UTF-8"
export LC_TIME="zh_CN.UTF-8"
export xcpretty=/usr/local/bin/xcpretty # xcpretty 的装置方位能够在终端用 which xcpretty找到
searchFunctionName=`searchProjectName`
path=${searchFunctionName}
# 字符串替换函数。//表明全局替换 /表明匹配到的第一个成果替换。
path=${path//.\//} # ./BridgeLabiPhone.xcodeproj -> BridgeLabiPhone.xcodeproj
path=${path//.xcodeproj/} # BridgeLabiPhone.xcodeproj -> BridgeLabiPhone
myworkspace=$path".xcworkspace" # workspace姓名
myscheme=$path # scheme姓名
# 铲除前次编译数据
if [ -d ./derivedData ]; then
echo -e $COLOR_SUCC'-----铲除前次编译数据derivedData-----'$COLOR_SUCC
rm -rf ./derivedData
fi
# xcodebuild clean
xcodebuild -scheme $myscheme -workspace $myworkspace clean
# # 生成编译数据
xcodebuild -scheme $myscheme -workspace $myworkspace -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 12 Pro Max' -configuration Debug GCC_PRECOMPILE_PREFIX_HEADER=YES CLANG_ENABLE_MODULE_DEBUGGING=NO COMPILER_INDEX_STORE_ENABLE=NO OTHER_CFLAGS="-DNS_FORMAT_ARGUMENT(A)= -D_Nullable_result=_Nullable" | tee xcodebuild.log | xcpretty -r json-compilation-database -o compile_commands.json
if [ -f ./compile_commands.json ]; then
echo -e $COLOR_SUCC'编译数据生成结束'$COLOR_SUCC
else
echo -e $COLOR_ERR'编译数据生成失利'$COLOR_ERR
return -1
fi
echo -e $COLOR_SUCC'OCLint代码剖析开端'$COLOR_SUCC
# 生成报表
oclint-json-compilation-database -e Pods -- -report-type pmd \
-rc=LONG_CLASS=1500 \
-rc=NESTED_BLOCK_DEPTH=5 \
-rc=LONG_VARIABLE_NAME=80 \
-rc=LONG_METHOD=200 \
-rc=LONG_LINE=300 \
-disable-rule ShortVariableName \
-disable-rule ObjCAssignIvarOutsideAccessors \
-disable-rule AssignIvarOutsideAccessors \
-allow-duplicated-violations=false\
-max-priority-1=100000 \
-max-priority-2=100000 \
-max-priority-3=100000 >> oclint.xml
echo -e $COLOR_SUCC'Infer代码剖析开端'$COLOR_SUCC
# --skip-analysis-in-path 是疏忽扫描目录
infer run --skip-analysis-in-path Pods --keep-going --compilation-database compile_commands.json
product_name=`sed -n '/PRODUCT_NAME/{s/PRODUCT_NAME = //;s/;//;s/^[[:space:]]*//;s/\"//g;p;q;}' ./$myscheme.xcodeproj/project.pbxproj`
version_number=`sed -n '/MARKETING_VERSION/{s/MARKETING_VERSION = //;s/;//;s/^[[:space:]]*//;s/\"//g;p;q;}' ./$myscheme.xcodeproj/project.pbxproj`
if [ -f ./oclint.xml -a -f ./infer-out/report.json ]; then
rm compile_commands.json
echo -e $COLOR_SUCC'代码剖析结束'$COLOR_SUCC
mv oclint.xml sonar-reports/
echo -e $COLOR_SUCC'移动至sonar-reports结束'$COLOR_SUCC
echo -e $COLOR_SUCC'开端履行sonar-scanner扫描文件'$COLOR_SUCC
echo -e $COLOR_SUCC'版别号:'${version_number}''$COLOR_SUCC
sonar-scanner -Dsonar.projectVersion=$version_number
else
echo -e $COLOR_ERR'剖析失利'$COLOR_ERR
curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=31ef53a9-1e97-42c6-9cc7-fb4432bd41f9' \
-H 'Content-Type: application/json' \
-d '
{
"msgtype": "text",
"text": {
"content": "APP称号:'${product_name}'\nAPP版别:'${version_number}'\nOCLint剖析失利"
}
}'
return -1
fi
local_ip = $(ifconfig | grep '\<inet\>'| grep -v '127.0.0.1' | awk '{ print $2}' | awk 'NR==1')
curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=31ef53a9-1e97-42c6-9cc7-fb4432bd41f9' \
-H 'Content-Type: application/json' \
-d '
{
"msgtype":"news",
"news":{
"articles":[
{
"title":"SonarQube 静态代码扫描完结",
"url":"http://'${local_ip}':9000/",
"description":"APP称号:'${product_name}'\nAPP版别:'${version_number}'扫描代码分支:'$1'",
"picurl":"https://csdn-app.csdn.net/1024store_1024pt.png"
}
]
}
}'
}
oclintForProject $1
- Jenkins 装备PMD(当然有了SonarQube,PMD就很鸡肋了)