add the static analysis target with a quick explaination

This commit is contained in:
ttroy50
2015-11-21 22:52:04 +00:00
parent 225599e769
commit f832bfa602
8 changed files with 337 additions and 1 deletions

View File

@@ -0,0 +1,26 @@
cmake_minimum_required (VERSION 2.6)
project(cppcheck_analysis)
# Add a custom CMake Modules directory
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules
${CMAKE_MODULE_PATH})
# find the cppcheck binary
find_package(CppCheck)
# static analysis. Should be before adding subprojects
set (ALL_ANALYSIS_TARGETS)
# Add sub directories
add_subdirectory(subproject1)
add_subdirectory(subproject2)
# Add the "make analysis" target
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()

View File

@@ -1 +1,180 @@
Example of calling the cppcheck tool
# CppCheck Static Analysis
This example is for calling the cppcheck tool to do static analysis.
It shows how to add cppcheck with a target for each sub-projects and also how to generate an overall "make analysis" target to do static analysis on all sub-projects.
## Requirements
To run this example you must have the CppCheck utility installed. On Ubuntu you can install it as
```
$ sudo apt-get install cppcheck
```
## Concepts
### Adding Custom Package Modules
#### Adding a custom module
The cmake/modules/FindCppCheck.cmake file contains the code to find and add variables for a custom package module. Custom modules can be used to find programs, libraries and header files to include in your program.
```
find_program(CPPCHECK_BIN NAMES cppcheck)
```
Search the path for the program "cppcheck" and store the result in the CPPCHECK_BIN variable
```
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.
```
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
```
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules
${CMAKE_MODULE_PATH})
```
The ${CMAKE_MODULE_PATH} points towards a folder which contains custom cmake modules.
To then add the package module you can call
```
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:
```
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.
```
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.
```
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.
```
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.
```
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.
```
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:
```
include(${CMAKE_SOURCE_DIR}/cmake/analysis.cmake)
add_analysis(${PROJECT_NAME} SOURCES)
```
### Creating a target to call other targets
In the root 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 root CMakeLists.txt
First add an empty variable ALL_ANALYSIS_TARGETS before calling your add_subdirectories() function.
```
set (ALL_ANALYSIS_TARGETS)
```
Second add the following after your add_subdirectories() call.
```
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.
## 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
│   │   ├── project
│   │   │   ├── CMakeLists.txt
│   │   │   ├── main.cpp
│   │   ├── project
│   │   │   ├── CMakeLists.txt
│   │   │   ├── main.cpp
```
You must add the following to the src/CMakeLists.txt file to correctly generate the "make analysis" target
```
set(analysis_TARGETS "${analysis_TARGETS}" PARENT_SCOPE)
```

View File

@@ -0,0 +1,50 @@
# Make sure cppcheck binary is available
if( NOT CPPCHECK_FOUND )
find_package(CppCheck)
endif()
# add a target for CppCheck
# _target - The name of the project that this is for. Will generate ${_target}_analysis
# _sources - The name of the variable holding the sources list.
# This is the name of the variable not the actual list
#
# Macro instead of function to make the PARENT_SCOPE stuff easier
macro(add_analysis _target _sources)
if( CPPCHECK_FOUND )
# Get the include files to also feed to cppcheck
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
foreach(dir ${dirs})
LIST(APPEND cppcheck_includes "-I${dir}")
endforeach()
# Add to the all target to have a high level "make analysis"
LIST(APPEND ALL_ANALYSIS_TARGETS "${_target}_analysis")
set(ALL_ANALYSIS_TARGETS "${ALL_ANALYSIS_TARGETS}" PARENT_SCOPE)
# This is used to make the command run correctly on the command line.
# The COMMAND argumetn expects a list and this does the change
# I need to check which version works with 2.7
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 ()
# add a custom _target_analysis target
add_custom_target(${_target}_analysis)
set_target_properties(${_target}_analysis PROPERTIES EXCLUDE_FROM_ALL TRUE)
# add the cppcheck command to the target
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)
message("adding cppcheck analysys target for ${_target}")
endif()
endmacro()

View File

@@ -0,0 +1,37 @@
# Locate cppcheck
#
# This module defines
# CPPCHECK_FOUND, if false, do not try to link to cppcheck --- if (CPPCHECK_FOUND)
# CPPCHECK_BIN, where to find cppcheck
#
# Exported argumets include
# CPPCHECK_THREADS
# CPPCHECK_ARG
#
# find the cppcheck binary
find_program(CPPCHECK_BIN NAMES cppcheck)
#
# Arugments are
# -j use multiple threads (and thread count)
# --quite only show errors / warnings etc
# --error-exitcode The code to exit with if an error shows up
# --enabled Comman seperated list of the check types. Can include warning,performance,style
# Note nightly build on earth changes error-exitcode to 0
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")
# handle the QUIETLY and REQUIRED arguments and set YAMLCPP_FOUND to TRUE if all listed variables are TRUE
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)

View File

@@ -0,0 +1,14 @@
# Set the project name
project (subproject1)
# Create a sources variable with a link to all cpp files to compile
set(SOURCES
main1.cpp
)
# include the file with the function then call the macro
include(${CMAKE_SOURCE_DIR}/cmake/analysis.cmake)
add_analysis(${PROJECT_NAME} SOURCES)
# Add an executable with the above sources
add_executable(${PROJECT_NAME} ${SOURCES})

View File

@@ -0,0 +1,7 @@
#include <iostream>
int main(int argc, char *argv[])
{
std::cout << "Hello Main1!" << std::endl;
return 0;
}

View File

@@ -0,0 +1,14 @@
# Set the project name
project (subproject2)
# Create a sources variable with a link to all cpp files to compile
set(SOURCES
main2.cpp
)
# include the file with the function then call the macro
include(${CMAKE_SOURCE_DIR}/cmake/analysis.cmake)
add_analysis(${PROJECT_NAME} SOURCES)
# Add an executable with the above sources
add_executable(${PROJECT_NAME} ${SOURCES})

View File

@@ -0,0 +1,9 @@
#include <iostream>
int main(int argc, char *argv[])
{
std::cout << "Hello Main2!" << std::endl;
char tmp[10];
tmp[11] = 's';
return 0;
}