我有一个结构类似的项目:
├── CMakeLists.txt
├── src
│ ├── logic.cpp
│ └── logic.h
└── test
├── CMakeLists.txt
└── logic_test.cpp
主要
CMakeLists.txt
文件是:
cmake_minimum_required (VERSION 2.8)
project (Logic)
set (Logic_SOURCES ${PROJECT_SOURCE_DIR}/src/logic.cpp)
include_directories (${PROJECT_SOURCE_DIR}/src)
add_library (logic SHARED ${Logic_SOURCES})
add_subdirectory (test)
测试的
CMakeLists.txt
是:
find_package (GTest)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -march=native -mtune=native -fprofile-arcs -ftest-coverage")
set (CMAKE_EXE_LINKER_FLAGS "-fprofile-arcs -ftest-coverage")
set (Test_SOURCES ${Logic_SOURCES} ${PROJECT_SOURCE_DIR}/test/logic_test.cpp)
add_executable (logic_test ${Test_SOURCES})
target_link_libraries (${TestName} gtest gtest_main gcov pthread)
为了处理测试覆盖率报告,我已将自定义目标添加到
test/CMakeLists.txt
:
set (Coverage_REPORT ${PROJECT_BINARY_DIR}/coverage.info)
set (Coverage_DIR ${PROJECT_BINARY_DIR}/coverage)
add_custom_command (
OUTPUT ${Coverage_REPORT}
COMMAND lcov -q -c -f -b . -d ${PROJECT_BINARY_DIR}/test -o ${Coverage_REPORT}
COMMAND lcov -e ${Coverage_REPORT} '${PROJECT_SOURCE_DIR}/src/*' -o ${Coverage_REPORT}
COMMAND genhtml ${Coverage_REPORT} --legend --demangle-cpp -f -q -o ${Coverage_DIR}
DEPENDS logic_test
)
add_custom_target (coverage DEPENDS ${Coverage_REPORT})
所有这些代码都正确且符合预期。工作流程如下所示:
mkdir build
cd build
cmake ..
make
./test/logictest
make coverage
但现在我想将测试覆盖工件添加到
make clean
规则中。我尝试将此代码添加到test/CMakeLists.txt
:
file (GLOB_RECURSE Test_GCNOS ${PROJECT_BINARY_DIR}/*.gcno)
file (GLOB_RECURSE Test_GCDAS ${PROJECT_BINARY_DIR}/*.gcda)
list (APPEND Test_COVERAGE_DATA "${Coverage_REPORT}")
list (APPEND Test_COVERAGE_DATA "${Coverage_DIR}")
list (APPEND Test_COVERAGE_DATA "${Coverage_GCNO}")
list (APPEND Test_COVERAGE_DATA "${Coverage_GCDA}")
set_directory_properties (PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${Test_COVERAGE_DATA}")
但是这种方法并没有达到预期的效果(对我来说)。当调用
cmake ..
时,工件尚不存在,因此变量 Coverage_DATA
为空,这需要在运行测试后调用 cmake ..
。这看起来很丑(对我来说)。
所以我的问题是:如何将测试覆盖工件添加到
make clean
规则中?
我编写了一个代码覆盖率 cmake 脚本,如果您正在做一个开源项目,它很容易与 http://coveralls.io/ 一起使用:
https://github.com/JoakimSoderberg/coveralls-cmake https://github.com/JoakimSoderberg/coveralls-cmake-example
但是,如果您想要一个简单地在本地生成覆盖数据的脚本,可以使用此项目:
https://github.com/bilke/cmake-modules/blob/master/CodeCoverage.cmake
我解决了清理覆盖数据的问题,方法是在生成新数据之前始终先将其从覆盖目标中删除。这不是一个干净的解决方案,但工作正常:
file(REMOVE_RECURSE ${PROJECT_BINARY_DIR}/*.gcda)
(我几乎从不使用 make clean,而是使用多个构建目录和偶尔的
rm -rf *
。在我看来,make clean 的整个概念被打破了,将构建和源代码分开使事情变得更加干净)
这不能称为优雅的解决方案,但在我看来,它比
cmake
回忆更好。
我尝试创建宏,通过源文件列表确定覆盖工件的名称(*.gcno
和*.gcda
文件):
macro (determine_coverage_data Sources TestName Artifacts Suffix)
set (CoverageDirectory "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${TestName}.dir")
foreach (File ${Sources})
string (REGEX MATCH "^${CMAKE_CURRENT_SOURCE_DIR}*" Directory "${File}")
if (Directory STREQUAL CMAKE_CURRENT_SOURCE_DIR)
string (REGEX REPLACE "^${CMAKE_CURRENT_SOURCE_DIR}*" "${CoverageDirectory}" File "${File}")
else (Directory STREQUAL CMAKE_CURRENT_SOURCE_DIR)
string (REGEX REPLACE "/" ";" A "${CMAKE_CURRENT_SOURCE_DIR}")
string (REGEX REPLACE "/" ";" B "${File}")
list (LENGTH A DeepDirectory)
list (LENGTH B DeepFile)
set (File "${CoverageDirectory}")
set (I 1)
while (I LESS DeepDirectory)
list (GET A ${I} AI)
list (GET B ${I} BI)
if (AI STREQUAL BI)
math (EXPR I "${I} + 1")
else (AI STREQUAL BI)
math (EXPR DeepDiff "${DeepFile} - ${I} - 1")
while (DeepDiff GREATER 0)
set (File "${File}/__")
math (EXPR DeepDiff "${DeepDiff} - 1")
endwhile (DeepDiff GREATER 0)
while (I LESS DeepFile)
list (GET B ${I} BI)
set (File "${File}/${BI}")
math (EXPR I "${I} + 1")
endwhile (I LESS DeepFile)
endif (AI STREQUAL BI)
endwhile (I LESS DeepDirectory)
endif (Directory STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set (${Artifacts} ${${Artifacts}} "${File}${Suffix}")
endforeach (File)
endmacro (determine_coverage_data)
完整的解决方案如下所示:
├── cmake
│ └── UseGCov.cmake
├── CMakeLists.txt
├── src
│ ├── logic.cpp
│ └── logic.h
└── test
├── CMakeLists.txt
└── logic_test.cpp
主要
CMakeLists.txt
文件是:
cmake_minimum_required (VERSION 2.8)
project (Logic)
set (Logic_SOURCES ${PROJECT_SOURCE_DIR}/src/logic.cpp)
# Include macro.
include ("${PROJECT_SOURCE_DIR}/cmake/UseGCov.cmake")
include_directories (${PROJECT_SOURCE_DIR}/src)
add_library (logic SHARED ${Logic_SOURCES})
add_subdirectory (test)
测试的
CMakeLists.txt
是:
find_package (GTest)
set (Test_SOURCES ${Logic_SOURCES} ${PROJECT_SOURCE_DIR}/test/logic_test.cpp)
set (TestName logic_test)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -march=native -mtune=native -fprofile-arcs -ftest-coverage")
set (CMAKE_EXE_LINKER_FLAGS "-fprofile-arcs -ftest-coverage")
# Call macro.
determine_coverage_data ("${Test_SOURCES}" "${TestName}" Test_GCNOS ".gcno")
determine_coverage_data ("${Test_SOURCES}" "${TestName}" Test_GCDAS ".gcda")
set (Coverage_REPORT ${PROJECT_BINARY_DIR}/coverage.info)
set (Coverage_DIR ${PROJECT_BINARY_DIR}/coverage)
add_custom_command (
OUTPUT ${Coverage_REPORT}
COMMAND lcov -q -c -f -b . -d ${PROJECT_BINARY_DIR}/test -o ${Coverage_REPORT}
COMMAND lcov -e ${Coverage_REPORT} '${PROJECT_SOURCE_DIR}/src/*' -o ${Coverage_REPORT}
COMMAND genhtml ${Coverage_REPORT} --legend --demangle-cpp -f -q -o ${Coverage_DIR}
DEPENDS logic_test
)
add_custom_target (coverage DEPENDS ${Coverage_REPORT})
set (Test_COVERAGE_DATA ${Coverage_REPORT} ${Coverage_DIR} ${Test_GCNOS} ${Test_GCDAS})
set_directory_properties (PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${Test_COVERAGE_DATA}")
add_executable (${TestName} ${Test_SOURCES})
target_link_libraries (${TestName} gtest gtest_main gcov pthread)
cmake_minimum_required(VERSION 3.20)
project(A)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
Set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fno-inline")
add_executable(main main.cpp Hello.cpp)
add_executable(main2 main.cpp Hello.cpp)
list(APPEND test_targets main main2)
foreach(test_target IN LISTS test_targets)
get_property(target_srcs TARGET ${test_target} PROPERTY SOURCES)
foreach(target_src IN LISTS target_srcs)
add_custom_command(OUTPUT ./CMakeFiles/${test_target}.dir/clear_${test_target}_${target_src}_coverage.h
DEPENDS ${target_src}
COMMAND ${CMAKE_COMMAND} -E echo "clearing ${target_src}.gcno"
COMMAND ${CMAKE_COMMAND} -E rm -f ./${target_src}.gcno
COMMAND ${CMAKE_COMMAND} -E touch clear_${test_target}_${target_src}_coverage.h
WORKING_DIRECTORY ./CMakeFiles/${test_target}.dir
)
target_sources(${test_target} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${test_target}.dir/clear_${test_target}_${target_src}_coverage.h")
list(APPEND ${test_target}_gcda ./${target_src}.gcda)
endforeach()
add_custom_target(clear_${test_target}_gcda
COMMAND ${CMAKE_COMMAND} -E echo "clearing ${${test_target}_gcda}"
COMMAND ${CMAKE_COMMAND} -E rm -f "${${test_target}_gcda}"
WORKING_DIRECTORY ./CMakeFiles/${test_target}.dir
COMMAND_EXPAND_LISTS)
add_dependencies(${test_target} clear_${test_target}_gcda)
endforeach()
我为此问题编写了一个简单的代码。我使用 add_custom_command() 和一个空文件作为删除 .gcno 文件的标志。这种方法有一些优点。当源文件发生更改时,它只会在重新编译源文件之前删除相应的文件。源文件编译完成后,新的.gcno
文件将创建成功。
.gcno
文件。
因此,我可以轻松运行
.gcda
来重新生成更改的 cmake --build .
文件并删除所有
.gcno
文件。