OCLint
OCLint is a fantastic static code analysis tool for improving quality and reducing defects by inspecting C, C++ and Objective-C code.
I’m not going to copy-paste the rest of oclint.org here, check it out to see what the tool can do. In this article you will learn how to set it up and use for Mobile CI.
OCLint is often referred as a lint tool, but in the end of the day it’s a static analysis tool, meaning it analyses your code statically, that is without running it.
Install
For quite a while I though OCLint wasn’t available via Homebrew package manager. While it technically isn’t there yet, it’s supported by Homebrew Cask. If you never used it yet, Homebrew Cask is an extension for Homebrew and lets you to manage OS X applications just like packages. Another benefit of this tool is that it includes other packages like OCLint which are not yet available in Homebrew.
brew install caskroom/cask/brew-cask
Now install OCLint
brew cask install oclint
Check the installation, it’s now sitting in usr/local/
.
Use
I will write about using OCLint with xcodebuild
, basically rewording official documentation. OCLint supports all other tools, check out documentation for details.
Using OCLint with xcodebuild
requires 3 steps.
- Build and capture output.
- Generate compile commands database (JSON format) for OCLint using
xcodebuild
output. - Run OCLint using generated JSON database.
The first step is straightforward, run xcodebuild
the way you would run it normally, the only difference is that you want to capture build output. Use tee
command to do that, for example
xcodebuild clean build -project MyProject -scheme MyScheme -configuration Debug | tee xcodebuild.log
It is recommended to use Debug configuration for static analysis.
Next, generate compilation commands database with oclint-xcodebuild
command. This command expects a file named xcodebuild.log
by default, but in the example I will pass it explicitly. The default output file name is compile_commands.json
but I’ll be more verbose anyway and specify output file name.
oclint-xcodebuild -output compile_commands.json xcodebuild.log
Optionally you can use -e | -exclude
option and pass regular expression which will exclude the files you are not interested in, such as 3rd party source code.
Finally, use oclint-json-compilation-database
to run oclint and generate report.
oclint-json-compilation-database \
-exclude ${LINT_EXCLUDES} \
-- \
-report-type ${LINT_REPORT_TYPE} \
${LINT_RULES} \
${LINT_DISABLE_RULES} \
${LINT_THRESHOLD} \
-o ${LINT_REPORTS_DIR}/${LINT_REPORT_FILE} \
-stats \
-verbose \
-list-enabled-rules
Let’s analyze this call in details and expand actual values of all environment variables.
First you call oclint-json-compilation-database
and specify it’s own arguments. In this case it is a list of excludes passed as ${LINT_EXCLUDES}
. OCLint understands grep-like regular expressions syntax, for example
LINT_EXCLUDES="Libraries|lib|Pods|Carthage"
The -i [INCLUDES]
option is used to do the opposite to exclude.
The double dash --
indicates the start of arguments which will be passed to invocation of oclint
command.
Report type (LINT_REPORT_TYPE
) specified via -report-type
can be one of text
, html
, xml
, json
and pmd
. For CI tasks you should definitely use PMD report type. While running locally stick with html
. So you should set LINT_REPORT_TYPE
to one of these values. Read the full documentation for more.
Lint rules (LINT_RULES
) is an option with which you can override default threshold values for rules. For example, Objective-C is very verbose language, so let’s increase thresholds for the the max length of line of code, name of method and name of variable. This is how it will look in shell script
# Rules.
LINT_LONG_LINE=300
LINT_LONG_VARIABLE_NAME=64
LINT_LONG_METHOD=150
LINT_RULES="-rc LONG_LINE=${LINT_LONG_LINE} \
-rc LONG_VARIABLE_NAME=${LINT_LONG_VARIABLE_NAME} \
-rc LONG_METHOD=${LINT_LONG_METHOD}"
You may want to disable some rules as well. This could be useful when working with some legacy code which smells a lot and there are no plans to fix it ever. In cases like this it’s easier to silent some warnings. For example, let’s disable warning about the use of _ivarName
outside of accessors and initializers. Let’s also ignore useless parentheses and warnings about unused method parameters.
# Disable rules.
LINT_DISABLE_RULES="-disable-rule=UnusedMethodParameter \
-disable-rule=UselessParentheses \
-disable-rule=IvarAssignmentOutsideAccessorsOrInit"
Full list of rules for further reference.
Note!: The names of the rules on documentation page do not match the names you should be using to disable them.
For example CoveredSwitchStatementsDontNeedDefault, should be specified as SwitchStatementsDon\'TNeedDefaultWhenFullyCovered
when used in command line scripts (note escaping the '
too). To get the correct names use --list-enabled-rules
option, which is mentioned further in the post.
Threshold allows you to control when analysis passes or fails. There are 3 priorities (Priority 1, 2 and 3) and each comes with default thresholds (0, 10 and 20). You can use -max-priority-<N>
command line option to customize priority N
threshold. This is how you would change it to 0, 20 and 30 correspondingly
# Threshold.
LINT_PRIORITY_1_THRESHOLD=0
LINT_PRIORITY_2_THRESHOLD=20
LINT_PRIORITY_3_THRESHOLD=30
LINT_THRESHOLD = "-max-priority-1=${LINT_PRIORITY_1_THRESHOLD} \
-max-priority-2=${LINT_PRIORITY_2_THRESHOLD} \
-max-priority-3=${LINT_PRIORITY_3_THRESHOLD}"
Output can be specified via -o
option. If you are using PMD report type, write out to XML file, for HTML report, write to HTML format. Better create a separate directory for reports, for example
# Reports.
LINT_REPORTS_DIR=oclint-reports
# Use .html for HTML report type.
LINT_REPORT_FILE=oclint.xml
mkdir -p ${LINT_REPORTS_DIR}
Finally add some verbosity with -stats
to output statistics, -list-enabled-rules
to eyeball the rules being used and -verbose
for what it stands.
Config File
Instead of having lengthy shell scripts, you can opt out to using .oclint
config file. Put it in you project’s root folder and list all the thresholds and other rules configs it YAML format, for example:
# OCLint config example
rules:
disable-rules:
- GotoStatement
- UnusedMethodParameter
- EmptyIfStatement
- SwitchStatementsDon'TNeedDefaultWhenFullyCovered
rule-configurations:
- key: LONG_LINE
value: 300
- key: SHORT_VARIABLE_NAME
value: 1
- key: LONG_VARIABLE_NAME
value: 64
max-priority-1: 0
max-priority-2: 20
max-priority-3: 30
enable-clang-static-analyzer: false
Read official documentation for more details.
Other Notes
Integrate with Xcode
There are ways to integrate OCLint with Xcode. This is actually worth a separate post on its own. The goal is to allow non-script-and-command-line-savvy teammates to run OCLint analyzer in Xcode. Don’t set your hopes too hight though, they just won’t do that anyway :) However, check this post for some examples.
Suppress Warnings
Sometimes you just have to violate the rule. Let’s say it’s a legacy code base and there’s just this one violation and nothing you can do about it. Instead of disabling a useful rule completely, consider suppressing the warning.
Note: 0.10.1 doesn’t understand //!OCLint
suppress comments. It is case-sensitive and must be //!OCLINT
. This should be fixed in next releases though.
Save Time
Running a project build to generate Xcode build log, then running OCLint is very time consuming task. Consider generating Xcode build log once and then reusing it for OCLint. You will have to generate fresh build log when the project structure changes.
If you run unit tests as part of CI pipeline, then capture build log when building unit tests target.
Summary
Give it a go. You now have a very powerful tool in your tool belt. Figure out your custom project thresholds, integrate it as part of your project makefile or Fastfile, run it as CI job and output reports using one of the plugins available.
You may actually unearth a pair of real bugs in your code.