xcodebuild+OCLint实践

Using OCLint with xcodebuild

简介

作者在一家头部体育直播公司赛事直播业务,高度组件化带来便利的同时,有时候Xcode的抽风也是让人很是头疼,其中有一次就是写了一个带有返回值的函数,里面有多个case分支,有一个分支没有返回,也没注意到,Xcode竟然毫无报错甚至警告也没有。。review同事也没有很细致的注意到,导致最后灰度期间打tag报错浪费了很多不必要的时间,作者也很是尴尬。Xcode静态检查也没有报错提示。

所以笔者研究了一翻靠谱的方案,决定借助OCLint结合xcodebuild Release环境下全面检查下自己负责业务块儿的代码质量和规范问题

大家可以去OCLint详细了解下

OCLint是一个静态代码扫描分析工具,提高代码质量和减少潜在的缺陷,目前支持C,C++,Objective-C

主要功能:


1.可能的bugs - 空的if/else/try/catch/finally语句

2.未使用的代码段 - 未使用的局部变量和参数等

3.过于复杂的代码 - 圈逻辑复杂度、函数所有可能的执行路径的数量大小、有效代码行高低等

4.冗余的代码 - 比如冗余的if语句和无用的括号等

5.代码异味 (Code smells)

- 比如超长的方法和参数列表;如果一段代码是不稳定或者有一些潜在问题的,那么代码往往会包含一些明显的痕迹。

正如食物要腐坏之前,经常会发出一些异味一样。

6.错误的做法 - 比如反转逻辑,参数的重新分配

7. ...

安装OCLint

笔者通过brew来安装


brew tap oclint/formulae     

brew install oclint

每次使用brew安装软件时,默认都会自动检查更新Homebrew,显示Updating Homebrew...,会浪费很多的时间,所以要关闭自动更新,在命令行执行如下命令即可在当前命令行会话关闭自动更新:

export HOMEBREW_NO_AUTO_UPDATE=true

如果想重启后依然生效,需要把上面这行加入到环境变量中去,编辑/etc/profile

sudo vim /etc/profile或者~/.base_profile

在末尾加上如下内容即可

export HOMEBREW_NO_AUTO_UPDATE=true

通过brew安装如果遇到问题:

  /usr/local/include is not writable

尝试执行sudo chown -R $(whoami) /usr/local/

  如果提示chown: /usr/local: Operation not permitted可能是更高Mac系统版本

试试执行sudo chown -R $(whoami) /usr/local/*
如果提示不行要看下是否是高版本mac兼容问题, /usr/local/下的include、include_old相关
其它安装方式参照官网OCLint安装

安装xcpretty

xcodebuild的输出阅读起来不是太直观,使用xcpretty可以解决这个问题,并且可以输出多种格式的日志信息


gem install xcpretty

xcodebuild编译并生成compile_commands.json

OCLint 结合 xcodebuild 的使用主要分为以下几个步骤

1.  使用 xcodebuild clean清理工程
2.  使用 xcodebuild 编译工程生成xcodebuild.log并且使用 xcpretty 输出编译报告为JSON Compilation Database format类型的compile_commands.json文件
3. oclint-json-compilation-database提取compile_commands.json文件信息并导出为指定格式的可视化编译分析报告

在我们xcodebuild之前最好先删除掉DerivedData下对应的编译索引缓存

下面以Demo为示例

xcodebuild clean
xcodebuild -workspace Demo.xcworkspace -scheme Demo -configuration Release | tee xcodebuild.log | xcpretty --report json-compilation-database --output compile_commands.json

笔者为pod组件工程,没有的话:

-workspace Demo.xcworkspace替换为-project Demo.xcodeproj

tee命令

  如果我们既想把输出保存到文件中,又想在屏幕上看到输出内容,就可以使用tee命令了。以上就是把屏幕的输出同时也保存在xcodebuild.log中

xcpretty可以导出JSON Compilation Database format类型的compile_commands.json文件

输出编译分析结果

使用 oclint-json-compilation-database 指令来解析compile_commands.json。

oclint-json-compilation-database --help查看使用


usage: oclint-json-compilation-database [-h] [-v] [-debug] [-i INCLUDES]

 [-e EXCLUDES] [-p build-path]

 [oclint_args [oclint_args ...]]

OCLint for JSON Compilation Database (compile_commands.json)

positional arguments:

 oclint_args  arguments that are passed to OCLint invocation

optional arguments:

 -h, --help show this help message and exit

 -v show invocation command with arguments

 -debug, --debug  invoke OCLint in debug mode

 -i INCLUDES, -include INCLUDES, --include INCLUDES

 extract files matching pattern

 -e EXCLUDES, -exclude EXCLUDES, --exclude EXCLUDES

 remove files matching pattern

 -p build-path  specify the directory containing compile_commands.json

ok接下来开始导出指定格式的分析报告


oclint-json-compilation-database -e Pods -- -report-type html -rc LONG_LINE=200 -rc LONG_VARIABLE_NAME=30 -disable-rule ShortVariableName -max-priority-1=10000 -max-priority-2=10000 -max-priority-3=10000 >> oclint_result.html

最终输出一个 HTML 类型的分析报告;当然也可以输出xml,pmd,xcode格式文件

-e Pods 忽略Pods文件夹(一些第三方库和其它私有库)。我们只关心我们自己的Framework工程的代码质量.

通常使用 -- 来分割 oclint-json-compilation-database 的参数与 oclint_args。oclint_args 就是 oclint 命令的参数

oclint --help来查看oclint指令支持的参数


USAGE: oclint [options] <source0> [... <sourceN>]

OPTIONS:

Generic Options:

 -help  - Display available options (-help-hidden for more)

 -help-list - Display list of available options (-help-list-hidden for more)

 -version - Display the version of this program

OCLint options:

 -R=<directory> - Add directory to rule loading path

 -allow-duplicated-violations - Allow duplicated violations in the OCLint report

 -disable-rule=<rule name>  - Disable rules

 -enable-clang-static-analyzer - Enable Clang Static Analyzer, and integrate results into OCLint report

 -enable-global-analysis  - Compile every source, and analyze across global contexts (depends on number of source files, could results in high memory load)

 -extra-arg=<string>  - Additional argument to append to the compiler command line

 -extra-arg-before=<string> - Additional argument to prepend to the compiler command line

 -list-enabled-rules  - List enabled rules

 -max-priority-1=<threshold>  - The max allowed number of priority 1 violations

 -max-priority-2=<threshold>  - The max allowed number of priority 2 violations

 -max-priority-3=<threshold>  - The max allowed number of priority 3 violations

 -no-analytics  - Disable the anonymous analytics

 -o=<path>  - Write output to <path>

 -p=<string>  - Build path

 -rc=<parameter>=<value>  - Override the default behavior of rules

 -report-type=<name>  - Change output report type

 -rule=<rule name>  - Explicitly pick rules

然后当前文件下多出来一个oclint_result.html文件

打开后部分显示:

build.png

以下是shell脚本供参考:

#!/bin/bash
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 LC_ALL=

function checkDepend () {
    command -v xcpretty >/dev/null 2>&1 || {
        echo >&2 "I require xcpretty but it's not installed.  Install:gem install xcpretty";
        exit
        }
    command -v oclint-json-compilation-database >/dev/null 2>&1 || {
        echo >&2 "I require oclint-json-compilation-database but it's not installed.  Install:brew install oclint";
        exit
        }
}

function oclintForProject () {
    checkDepend
    projectName=$1
    scheme=$2
    reportType=$3
    REPORT_PMD="pmd"
    REPORT_XCODE="xcode"
    REPORT_HTML="html"
    myworkspace=${projectName}
    myscheme=${scheme}
    echo "myworkspace是:${myworkspace}"
    echo "myscheme是:${myscheme}"
    echo "reportType为:${reportType}"

    if [ -d ./build ]; then
        rm -rf ./build
    fi

    xcodebuild clean
    echo -e "\033[34m 【 🔨 开始编译... 】  \033[0m"
    echo -e "\033[34m  xcodebuild  -workspace ${myworkspace}.xcworkspace -scheme ${myscheme} -sdk iphonesimulator -derivedDataPath ./build/derivedData  -configuration Release COMPILER_INDEX_STORE_ENABLE=NO | xcpretty -r json-compilation-database -o compile_commands.json  \033[0m"

    xcodebuild -workspace ${myworkspace} -scheme ${myscheme} -sdk iphonesimulator -derivedDataPath ./build/derivedData -configuration Release COMPILER_INDEX_STORE_ENABLE=NO | xcpretty -r json-compilation-database -o compile_commands.json
    

    if [ -f ./compile_commands.json ]
        then
        echo -e "\033[32m compile_commands.json已生成 \033[0m"
    else
        echo -e "\031[33m compile_commands.json生成失败 \033[0m"
        return -1
    fi

    echo -e "\033[34m 开始分析,生成报告中...\033[0m"
    exclude_files=("cardloan_js" "Pods")
    exclude=""
    for i in ${exclude_files[@]}; do
        exclude=${exclude}"-e "${i}" "
    done
    echo "exclude dic:${exclude}"

    if [[ ${reportType} =~ ${REPORT_PMD} ]]
    then
        nowReportType="-report-type pmd -o pmd.xml"
    elif [[ ${reportType} =~ ${REPORT_HTML} ]]
    then
        nowReportType="-report-type html"
    else
        nowReportType="-report-type xcode"
    fi
 
    echo -e "\033[34m  oclint-json-compilation-database ${exclude} -- ${nowReportType} -rc LONG_LINE=200 -disable-rule ShortVariableName -disable-rule ObjCAssignIvarOutsideAccessors -disable-rule AssignIvarOutsideAccessors -max-priority-1=100000 -max-priority-2=100000 -max-priority-3=100000 >> oclint_result.html \033[0m"
    
    oclint-json-compilation-database ${exclude} -- \
    ${nowReportType} \
    -rc LONG_LINE=200 \
    -disable-rule ShortVariableName \
    -disable-rule ObjCAssignIvarOutsideAccessors \
    -disable-rule AssignIvarOutsideAccessors \
    -max-priority-1=100000 \
    -max-priority-2=100000 \
    -max-priority-3=100000 >> oclint_result.html

#    rm compile_commands.json
    if [[ ${reportType} =~ ${REPORT_PMD} ]] && [ ! -f ./pmd.xml ]
    then
        echo -e "\033[31m 分析失败 \033[0m"
        return -1
    else
        echo -e "\033[32m 👏👏👏 完毕 \033[0m"
        return 0
    fi
}

myworkspace="ABC.xcworkspace"
myscheme="ABC"
# html/xcode/pmd
reportType="html"

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