mirror of
https://github.com/ttroy50/cmake-examples.git
synced 2025-12-18 12:14:36 +03:00
346 lines
9.8 KiB
Plaintext
346 lines
9.8 KiB
Plaintext
= CppCheck Static Analysis
|
|
:toc:
|
|
:toc-placement!:
|
|
|
|
toc::[]
|
|
|
|
# Introduction
|
|
|
|
This example shows how to call the
|
|
http://cppcheck.sourceforge.net/[CppCheck] tool to do static analysis.
|
|
|
|
It includes code to
|
|
|
|
* Find the cppcheck binary
|
|
* Add cppcheck with a target for each sub-projects
|
|
* Generate an overall `make analysis` target to do static
|
|
analysis on all sub-projects.
|
|
|
|
The files included in this example are:
|
|
|
|
```
|
|
cppcheck$ tree
|
|
.
|
|
├── cmake
|
|
│ ├── analysis.cmake
|
|
│ └── modules
|
|
│ └── FindCppCheck.cmake
|
|
├── CMakeLists.txt
|
|
├── subproject1
|
|
│ ├── CMakeLists.txt
|
|
│ └── main1.cpp
|
|
└── subproject2
|
|
├── CMakeLists.txt
|
|
└── main2.cpp
|
|
```
|
|
|
|
* CMakeLists.txt - Top level CMakeLists.txt
|
|
* cmake/analysis.cmake - Includes functions to add analysis targets
|
|
* cmake/modules/FindCppCheck.cmake - A custom package module to find CppCheck
|
|
* subproject1/CMakeLists.txt - CMake commands for subproject 1
|
|
* subproject1/main.cpp - source for a subproject with no errors
|
|
* subproject2/CMakeLists.txt - CMake commands for subproject 2
|
|
* subproject2/main2.cpp - source for a subproject that includes errors
|
|
|
|
# Requirements
|
|
|
|
To run this example you must have the CppCheck utility installed. On
|
|
Ubuntu you can install it as
|
|
|
|
[source,bash]
|
|
----
|
|
$ sudo apt-get install cppcheck
|
|
----
|
|
|
|
# Concepts
|
|
|
|
## Adding Custom Package Modules
|
|
|
|
Custom modules can be used to find programs, libraries and header files
|
|
to include in your program.
|
|
|
|
### Adding a custom module
|
|
|
|
The `cmake/modules/FindCppCheck.cmake` file contains the code to initialise a
|
|
custom package module.
|
|
|
|
The following is a breakdown of the file:
|
|
|
|
[source,cmake,numbered]
|
|
----
|
|
find_program(CPPCHECK_BIN NAMES cppcheck)
|
|
----
|
|
|
|
Search the path for the cppcheck binary. Once found store the result in the
|
|
+CPPCHECK_BIN+ variable
|
|
|
|
[source,cmake,numbered]
|
|
----
|
|
set (CPPCHECK_THREADS "-j 4" CACHE STRING "The -j argument to have cppcheck use multiple threads / cores")
|
|
|
|
set (CPPCHECK_ARG "${CPPCHECK_THREADS}" CACHE STRING "The arguments to pass to cppcheck. If set will overwrite CPPCHECK_THREADS")
|
|
----
|
|
|
|
Set some custom arguments that can be later passed to cppcheck.
|
|
|
|
[source,cmake]
|
|
----
|
|
include(FindPackageHandleStandardArgs)
|
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(
|
|
CPPCHECK
|
|
DEFAULT_MSG
|
|
CPPCHECK_BIN
|
|
CPPCHECK_THREADS
|
|
CPPCHECK_ARG)
|
|
|
|
mark_as_advanced(
|
|
CPPCHECK_BIN
|
|
CPPCHECK_THREADS
|
|
CPPCHECK_ARG)
|
|
----
|
|
|
|
Export the variables so that they can be seen from ccmake / cmake-gui
|
|
and set in the cache. By default these will not be visible unless the
|
|
view advanced flag is set.
|
|
|
|
### Setting path to custom modules
|
|
|
|
The default path that CMake will search for modules is `/usr/share/cmake/Modules`.
|
|
To include custom modules you must tell CMake where to search for them.
|
|
This can be done using the variable +${CMAKE_MODULE_PATH}+ which
|
|
contains the paths that CMake will search for modules.
|
|
|
|
[source,cmake]
|
|
----
|
|
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules
|
|
${CMAKE_MODULE_PATH})
|
|
----
|
|
|
|
|
|
To then add the package module to your CMakeLists.txt you can call
|
|
|
|
[source,cmake]
|
|
----
|
|
find_package(CppCheck)
|
|
----
|
|
|
|
## Parent Scope Variables
|
|
|
|
The scope of variables when they are declared / changed is typically in
|
|
the function of file the are called. To make a change to a variable
|
|
which is the caller of your scope, you should call it as follows:
|
|
|
|
[source,cmake]
|
|
----
|
|
set(ALL_ANALYSIS_TARGETS "${ALL_ANALYSIS_TARGETS}" PARENT_SCOPE)
|
|
----
|
|
|
|
## add_analysis macro
|
|
|
|
The +add_analysis()+ macro in `cmake/analysis.cmake` is the core idea for this
|
|
example. If cppcheck is available then a list of arguments are compiled
|
|
and added to a custom command that calls cppcheck on the sources. These
|
|
are then added to a custom target.
|
|
|
|
A breakdown of this macro is below:
|
|
|
|
[source,cmake]
|
|
----
|
|
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
|
|
foreach(dir ${dirs})
|
|
LIST(APPEND cppcheck_includes "-I${dir}")
|
|
endforeach()
|
|
----
|
|
|
|
Find the include files from and calls to +include_directories()+ in the
|
|
same project.
|
|
|
|
[source,cmake]
|
|
----
|
|
LIST(APPEND ALL_ANALYSIS_TARGETS "${_target}_analysis")
|
|
set(ALL_ANALYSIS_TARGETS "${ALL_ANALYSIS_TARGETS}" PARENT_SCOPE)
|
|
----
|
|
|
|
Export the target name into a variable that can later be used to add a
|
|
global `make analysis` target.
|
|
|
|
[source,cmake]
|
|
----
|
|
if (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VESION} GREATER 2.7)
|
|
separate_arguments(tmp_args UNIX_COMMAND ${CPPCHECK_ARG})
|
|
else ()
|
|
# cmake 2.6 has different arguments
|
|
string(REPLACE " " ";" tmp_args ${CPPCHECK_ARG})
|
|
endif ()
|
|
----
|
|
|
|
Change the +CPPCHECK_ARG+ so that the can be added to command correctly in
|
|
the custom command.
|
|
|
|
[source,cmake]
|
|
----
|
|
add_custom_target(${_target}_analysis)
|
|
set_target_properties(${_target}_analysis PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
|
----
|
|
|
|
Add a custom target with a name you have passed in followed by
|
|
_analysis. Do not include this in the all target.
|
|
|
|
[source,cmake]
|
|
----
|
|
add_custom_command(TARGET ${_target}_analysis PRE_BUILD
|
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
|
COMMAND ${CPPCHECK_BIN} ${tmp_args} ${cppcheck_includes} ${${_sources}}
|
|
DEPENDS ${${_sources}}
|
|
COMMENT "Running cppcheck: ${_target}"
|
|
VERBATIM)
|
|
----
|
|
|
|
Add a custom command which is called from the custom target added above.
|
|
This will call cppcheck with any includes, arguments and sources that
|
|
you have provided. The sources that are analysed come from a _sources
|
|
variable. This should be the name of the variable which holds your list
|
|
of source files.
|
|
|
|
To call the +add_analysis()+ marco add the following to your projects
|
|
CMakeLists.txt file:
|
|
|
|
[source,cmake]
|
|
----
|
|
include(${CMAKE_SOURCE_DIR}/cmake/analysis.cmake)
|
|
add_analysis(${PROJECT_NAME} SOURCES)
|
|
----
|
|
|
|
## Creating a target to call other targets
|
|
|
|
In the top level CMakeLists.txt a custom target is created, which will call
|
|
all other analysis targets. This allows you to call `make analysis` and
|
|
scan all sub projects.
|
|
|
|
To achieve this 2 things should be added to the top level CMakeLists.txt
|
|
|
|
First add an empty variable +ALL_ANALYSIS_TARGETS+ before calling your
|
|
+add_subdirectories()+ function.
|
|
|
|
[source,cmake]
|
|
----
|
|
set (ALL_ANALYSIS_TARGETS)
|
|
----
|
|
|
|
Second add the following after your +add_subdirectories()+ call.
|
|
|
|
[source,cmake]
|
|
----
|
|
if( CPPCHECK_FOUND )
|
|
add_custom_target(analysis)
|
|
ADD_DEPENDENCIES(analysis ${ALL_ANALYSIS_TARGETS})
|
|
set_target_properties(analysis PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
|
message("analysis analysis targets are ${ALL_ANALYSIS_TARGETS}")
|
|
endif()
|
|
----
|
|
|
|
This adds the "make analysis" target which calls all the sub-targets.
|
|
|
|
# Building the example
|
|
|
|
[source,bash]
|
|
----
|
|
$ mkdir build
|
|
|
|
$ cd build/
|
|
|
|
$ cmake ..
|
|
-- The C compiler identification is GNU 4.8.4
|
|
-- The CXX compiler identification is GNU 4.8.4
|
|
-- Check for working C compiler: /usr/bin/cc
|
|
-- Check for working C compiler: /usr/bin/cc -- works
|
|
-- Detecting C compiler ABI info
|
|
-- Detecting C compiler ABI info - done
|
|
-- Check for working CXX compiler: /usr/bin/c++
|
|
-- Check for working CXX compiler: /usr/bin/c++ -- works
|
|
-- Detecting CXX compiler ABI info
|
|
-- Detecting CXX compiler ABI info - done
|
|
-- Found CPPCHECK: /usr/bin/cppcheck
|
|
adding cppcheck analysys target for subproject1
|
|
adding cppcheck analysys target for subproject2
|
|
analysis analysis targets are subproject1_analysis;subproject2_analysis
|
|
-- Configuring done
|
|
-- Generating done
|
|
-- Build files have been written to: /home/matrim/workspace/cmake-examples/04-static-analysis/cppcheck/build
|
|
|
|
$ make analysis
|
|
Scanning dependencies of target subproject1_analysis
|
|
Running cppcheck: subproject1
|
|
Checking main1.cpp...
|
|
Built target subproject1_analysis
|
|
Scanning dependencies of target subproject2_analysis
|
|
Running cppcheck: subproject2
|
|
Checking main2.cpp...
|
|
[main2.cpp:7]: (error) Array 'tmp[10]' accessed at index 11, which is out of bounds.
|
|
Built target subproject2_analysis
|
|
Scanning dependencies of target analysis
|
|
Built target analysis
|
|
----
|
|
|
|
The above calls cppcheck in both subproject folders as
|
|
|
|
[source,bash]
|
|
----
|
|
...
|
|
cd /path/to/subproject1 && /usr/bin/cppcheck -j 4 main1.cpp
|
|
...
|
|
cd /path/to/subproject2 && /usr/bin/cppcheck -j 4 main2.cpp
|
|
...
|
|
----
|
|
|
|
The main1.cpp has no errors so will complete correctly, however the
|
|
main2.cpp includes an out-of-bounds error. This is show with a warning as
|
|
|
|
------------------------------------------------------------------------------------
|
|
[main2.cpp:7]: (error) Array 'tmp[10]' accessed at index 11, which is out of bounds.
|
|
------------------------------------------------------------------------------------
|
|
|
|
By default cppcheck only prints warnings and exits with a successful
|
|
exit code. If the +${CPPCHECK_ARG}+ variable is set with
|
|
`--error-exitcode=1`, the make analysis will finish early as follows.
|
|
|
|
[source,bash]
|
|
----
|
|
$ make analysis
|
|
Running cppcheck: subproject2
|
|
Checking main2.cpp...
|
|
[main2.cpp:7]: (error) Array 'tmp[10]' accessed at index 11, which is out of bounds.
|
|
make[3]: *** [subproject2_analysis] Error 1
|
|
make[2]: *** [subproject2/CMakeFiles/subproject2_analysis.dir/all] Error 2
|
|
make[1]: *** [CMakeFiles/analysis.dir/rule] Error 2
|
|
make: *** [analysis] Error 2
|
|
----
|
|
|
|
# Extra Notes
|
|
|
|
If you have a multiple folders levels, where one folder just points to
|
|
sub folders, such as below:
|
|
|
|
------------------------------
|
|
├── root
|
|
│ ├── CMakeLists.txt
|
|
│ ├── src
|
|
│ │ ├── CMakeLists.txt
|
|
│ │ ├── project1
|
|
│ │ │ ├── CMakeLists.txt
|
|
│ │ │ ├── main.cpp
|
|
│ │ ├── project2
|
|
│ │ │ ├── CMakeLists.txt
|
|
│ │ │ ├── main.cpp
|
|
------------------------------
|
|
|
|
To correctly generate the root `make analysis` target you must export the +ALL_ANALYSIS_TARGET+
|
|
variable to the parent scope in `src/CMakeLists.txt`.
|
|
|
|
[source,cmake]
|
|
----
|
|
add_subdirectory(project1)
|
|
add_subdirectory(project2)
|
|
set(ALL_ANALYSIS_TARGETS "${ALL_ANALYSIS_TARGETS}" PARENT_SCOPE)
|
|
----
|