问题
I'm in a project where CMake is used for managing the build process. In the project there are several executables which depends on components and these components are built as static libraries. There are also dependencies between these components. For each executable or component, only their own local includes are specified and includes from dependencies are resolved by using target_link_libraries(<target> <dependencies>)
.
So far so good. The problem is when PC-Lint shall be integrated into the environment. For each component, we are setting up a PC-Lint target, which runs static code analysis on the header/source files of that component. The problem is that PC-Lint requires a file as input that shall contain all include paths needed by the files analyzed. Since the component depends on other components, we have to retrieve all include paths recursively in some way to get all includes needed. We would like to use the same mechanism as target_link_libraries uses to resolve the include paths for PC-Lint as well. Is that possible?
Since we have a list of dependencies for each component, we can get the include paths from those dependencies and include them in the file for PC-Lint. But the real problem is when source_file1.cpp in component1 includes header_file2.h from component2 which in turn includes header_file3.h from component 3. In that example, PC-lint will complain about header_file3.h since component3's include path is not included in the file provided to PC-lint and the reason for that is that there is no dependency to component3 in component1 (target_link_library() normally solves those recursive dependencies).
From the beginning we thought that creating a file containing all include path in the project would do it. The problem in that case is that there are several files with the same name (for instance: main.h) and PC-Lint will pick the wrong one.
Is there a way in CMake to retrieve all include paths recursively, alternatively all dependencies recursively? Or does anyone know another solution to this problem?
Thank you!
回答1:
Here the general idea:
GET_DIRECTORY_PROPERTY(_inc_dirs INCLUDE_DIRECTORIES)
foreach(_one_inc_dir ${_inc_dirs})
list(APPEND PROJECT_INCLUDE_DIRS "-i\"${_one_inc_dir}\"")
endforeach(_one_inc_dir)
Then use PROJECT_INCLUDE_DIRS
with PC-Lint invocation.
Here you can find module to use with your build project. Just add following lines into your CMakeLists.txt
find_package(Lint)
... define your targets
add_pc_lint( target_name ${variable with list of your source files to check} )
回答2:
I found a solution to the problem myself. In the files generated by CMake when using "NMake Makefiles" as generator, there is a file for each target called "DependInfo.cmake". The file is located in CMakeFiles/.dir/DependInfo.cmake for each target. In that file there is a variable named "CMAKE_CXX_TARGET_INCLUDE_PATH" which contain all include paths needed by the target. What I ended up doing, was a powershell script parsing that file and just picked out those paths in that variable and output those to a file in the format PC-Lint wants it.
Powershell script:
$projectRoot = $args[0]
$dependInfoFile = $args[1]
$output = $args[2]
if (Test-Path $dependInfoFile)
{
$lines = Get-Content $dependInfoFile
Remove-Item $output -Force -ErrorAction SilentlyContinue
foreach ($line in $lines)
{
if ($line -eq "set(CMAKE_CXX_TARGET_INCLUDE_PATH")
{
$printToFile = $TRUE
}
elseif ($printToFile -and ($line -eq " )"))
{
$printToFile = $FALSE
break
}
elseif ($printToFile)
{
$line = $line.trim()
$line = $line.Substring(1,$line.length-2)
while ($line.Substring(0,3) -eq "../")
{
$line = $line.SubString(3)
}
$outLine = "-i`"$projectRoot`/$line`""
Add-Content $output $outLine
}
}
}
For PC-lint, I added a custom_target which runs this powershell script with correct DependInfo.cmake file and the name of the output file. This custom_target is added as a dependency to the normal lint target, which results in that the generation of the output file is made just before linting. (Notice that some paths could possibly be needed to be changed)
FindLint.cmake (FindLint.cmake is provided by cmake, but has been modified)
# This file contains functions and configurations for generating PC-Lint build
# targets for your CMake projects.
set(PC_LINT_EXECUTABLE "c:/lint/lint-nt.exe" CACHE STRING "full path to the pc-lint executable. NOT the generated lin.bat")
set(PC_LINT_CONFIG_DIR "c:/lint/" CACHE STRING "full path to the directory containing pc-lint configuration files")
set(PC_LINT_USER_FLAGS "-b" CACHE STRING "additional pc-lint command line options -- some flags of pc-lint cannot be set in option files (most notably -b)")
# a phony target which causes all available *_LINT targets to be executed
add_custom_target(ALL_LINT)
# add_pc_lint(target source1 [source2 ...])
#
# Takes a list of source files and generates a build target which can be used
# for linting all files
#
# The generated lint commands assume that a top-level config file named
# 'std.lnt' resides in the configuration directory 'PC_LINT_CONFIG_DIR'. This
# config file must include all other config files. This is standard lint
# behaviour.
#
# Parameters:
# - target: the name of the target to which the sources belong. You will get a
# new build target named ${target}_LINT
# - source1 ... : a list of source files to be linted. Just pass the same list
# as you passed for add_executable or add_library. Everything except
# C and CPP files (*.c, *.cpp, *.cxx) will be filtered out.
#
# Example:
# If you have a CMakeLists.txt which generates an executable like this:
#
# set(MAIN_SOURCES main.c foo.c bar.c)
# add_executable(main ${MAIN_SOURCES})
#
# include this file
#
# include(/path/to/pc_lint.cmake)
#
# and add a line to generate the main_LINT target
#
# if(COMMAND add_pc_lint)
# add_pc_lint(main ${MAIN_SOURCES})
# endif(COMMAND add_pc_lint)
#
function(add_pc_lint target)
get_directory_property(lint_include_directories INCLUDE_DIRECTORIES)
get_directory_property(lint_defines COMPILE_DEFINITIONS)
# let's get those elephants across the alps
# prepend each include directory with "-i"; also quotes the directory
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/lint_include/${target}.lnt "")
foreach(include_dir ${lint_include_directories})
file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/lint_include/${target}.lnt -i"${include_dir}"\n)
endforeach(include_dir)
# prepend each definition with "-d"
set(lint_defines_transformed)
foreach(definition ${lint_defines})
list(APPEND lint_defines_transformed -d${definition})
endforeach(definition)
# list of all commands, one for each given source file
set(pc_lint_commands)
foreach(sourcefile ${ARGN})
# only include c and cpp files
if( sourcefile MATCHES \\.c$|\\.cxx$|\\.cpp$ )
# make filename absolute
get_filename_component(sourcefile_abs ${sourcefile} ABSOLUTE)
# create command line for linting one source file and add it to the list of commands
list(APPEND pc_lint_commands
COMMAND ${PC_LINT_EXECUTABLE}
-i"${PC_LINT_CONFIG_DIR}" std.lnt
"-u" ${PC_LINT_USER_FLAGS}
"${CMAKE_CURRENT_BINARY_DIR}/lint_include/${target}.lnt"
${lint_defines_transformed}
${sourcefile_abs})
endif()
endforeach(sourcefile)
add_custom_target(${target}_LINT_INCLUDE
powershell.exe -File \"${CMAKE_MODULE_PATH}/generate_lint_include.ps1\" ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.dir/DependInfo.cmake ${CMAKE_CURRENT_BINARY_DIR}/lint_include/${target}.lnt)
# add a custom target consisting of all the commands generated above
add_custom_target(${target}_LINT ${pc_lint_commands} VERBATIM)
add_dependencies(${target}_LINT ${target}_LINT_INCLUDE)
# make the ALL_LINT target depend on each and every *_LINT target
add_dependencies(lint ${target}_LINT)
endfunction(add_pc_lint)
来源:https://stackoverflow.com/questions/35068305/pc-lint-needs-a-list-of-all-include-paths-for-the-files-to-be-scanned-how-to-ge