Add upddated cmake tools from source location (#4040)

This commit is contained in:
Allen Byrne 2024-03-04 08:53:22 -06:00 committed by GitHub
parent 8276bdbe09
commit 38c39381ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 969 additions and 368 deletions

View File

@ -1,11 +1,10 @@
# CMake Scripts <!-- omit in toc -->
[![pipeline status](https://git.stabletec.com/other/cmake-scripts/badges/master/pipeline.svg)](https://git.stabletec.com/other/cmake-scripts/commits/master)
[![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://git.stabletec.com/other/cmake-scripts/blob/master/LICENSE)
[![pipeline status](https://git.stabletec.com/other/cmake-scripts/badges/main/pipeline.svg)](https://git.stabletec.com/other/cmake-scripts/commits/main)
[![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://git.stabletec.com/other/cmake-scripts/blob/main/LICENSE)
This is a collection of quite useful scripts that expand the possibilities for building software with CMake, by making some things easier and otherwise adding new build types
- [C++ Standards `c++-standards.cmake`](#c-standards-c-standardscmake)
- [Sanitizer Builds `sanitizers.cmake`](#sanitizer-builds-sanitizerscmake)
- [Code Coverage `code-coverage.cmake`](#code-coverage-code-coveragecmake)
- [Added Targets](#added-targets)
@ -15,28 +14,15 @@ This is a collection of quite useful scripts that expand the possibilities for b
- [1b - Via target commands](#1b---via-target-commands)
- [Example 2: Target instrumented, but with regex pattern of files to be excluded from report](#example-2-target-instrumented-but-with-regex-pattern-of-files-to-be-excluded-from-report)
- [Example 3: Target added to the 'ccov' and 'ccov-all' targets](#example-3-target-added-to-the-ccov-and-ccov-all-targets)
- [Compiler Options `compiler-options.cmake`](#compiler-options-compiler-optionscmake)
- [AFL Fuzzing Instrumentation `afl-fuzzing.cmake`](#afl-fuzzing-instrumentation-afl-fuzzingcmake)
- [Usage](#usage-1)
- [Dependency Graph `dependency-graph.cmake`](#dependency-graph-dependency-graphcmake)
- [Required Arguments](#required-arguments)
- [OUTPUT_TYPE *STR*](#output_type-str)
- [OUTPUT\_TYPE *STR*](#output_type-str)
- [Optional Arguments](#optional-arguments)
- [ADD_TO_DEP_GRAPH](#add_to_dep_graph)
- [TARGET_NAME *STR*](#target_name-str)
- [OUTPUT_DIR *STR*](#output_dir-str)
- [Doxygen `doxygen.cmake`](#doxygen-doxygencmake)
- [Optional Arguments](#optional-arguments-1)
- [ADD_TO_DOC](#add_to_doc)
- [INSTALLABLE](#installable)
- [PROCESS_DOXYFILE](#process_doxyfile)
- [TARGET_NAME *STR*](#target_name-str-1)
- [OUTPUT_DIR *STR*](#output_dir-str-1)
- [INSTALL_PATH *STR*](#install_path-str)
- [DOXYFILE_PATH *STR*](#doxyfile_path-str)
- [Prepare the Catch Test Framework `prepare_catch.cmake`](#prepare-the-catch-test-framework-prepare_catchcmake)
- [Optional Arguments](#optional-arguments-2)
- [COMPILED_CATCH](#compiled_catch)
- [CATCH1](#catch1)
- [CLONE](#clone)
- [ADD\_TO\_DEP\_GRAPH](#add_to_dep_graph)
- [TARGET\_NAME *STR*](#target_name-str)
- [OUTPUT\_DIR *STR*](#output_dir-str)
- [Tools `tools.cmake`](#tools-toolscmake)
- [clang-tidy](#clang-tidy)
- [include-what-you-use](#include-what-you-use)
@ -45,15 +31,9 @@ This is a collection of quite useful scripts that expand the possibilities for b
- [clang-format](#clang-format)
- [cmake-format](#cmake-format)
## C++ Standards [`c++-standards.cmake`](c++-standards.cmake)
Using the functions `cxx_11()`, `cxx_14()`, `cxx_17()` or `cxx_20()` this adds the appropriate flags for both unix and MSVC compilers, even for those before 3.11 with improper support.
These obviously force the standard to be required, and also disables compiler-specific extensions, ie `--std=gnu++11`. This helps to prevent fragmenting the code base with items not available elsewhere, adhering to the agreed C++ standards only.
## Sanitizer Builds [`sanitizers.cmake`](sanitizers.cmake)
Sanitizers are tools that perform checks during a program's runtime and return issues, and as such, along with unit testing, code coverage and static analysis, are another tool to add to the programmer's toolbox. And, of course, like the previous tools, they are simple to add to any project using CMake, allowing any project and developer to quickly and easily use them.
Sanitizers are tools that perform checks during a programs runtime and returns issues, and as such, along with unit testing, code coverage and static analysis, is another tool to add to the programmers toolbox. And of course, like the previous tools, are tragically simple to add into any project using CMake, allowing any project and developer to quickly and easily use.
A quick rundown of the tools available, and what they do:
- [LeakSanitizer](https://clang.llvm.org/docs/LeakSanitizer.html) detects memory leaks, or issues where memory is allocated and never deallocated, causing programs to slowly consume more and more memory, eventually leading to a crash.
@ -72,16 +52,18 @@ A quick rundown of the tools available, and what they do:
- Division by zero
- Unreachable code
- [MemorySanitizer](https://clang.llvm.org/docs/MemorySanitizer.html) detects uninitialized reads.
- [Control Flow Integrity](https://clang.llvm.org/docs/ControlFlowIntegrity.html) is designed to detect certain forms of undefined behaviour that can potentially allow attackers to subvert the program's control flow.
These are used by declaring the `USE_SANITIZER` CMake variable as one of:
These are used by declaring the `USE_SANITIZER` CMake variable as string containing any of:
- Address
- Memory
- MemoryWithOrigins
- Undefined
- Thread
- Address;Undefined
- Undefined;Address
- Leak
- CFI
Multiple values are allowed, e.g. `-DUSE_SANITIZER=Address,Leak` but some sanitizers cannot be combined together, e.g.`-DUSE_SANITIZER=Address,Memory` will result in configuration error. The delimiter character is not required and `-DUSE_SANITIZER=AddressLeak` would work as well.
## Code Coverage [`code-coverage.cmake`](code-coverage.cmake)
@ -101,7 +83,7 @@ To enable, turn on the `CODE_COVERAGE` variable.
- GCOV/LCOV:
- ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter.
- ccov-${TARNGET_NAME} : Generates HTML code coverage report for the associated named target.
- ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target.
- ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report.
- ccov-all-capture : Generates an all-merged.info file, for use with coverage dashboards (e.g. codecov.io, coveralls).
- LLVM-COV:
@ -156,7 +138,7 @@ target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' t
```
add_executable(theExe main.cpp non_covered.cpp)
target_code_coverage(theExe EXCLUDE non_covered.cpp) # As an executable target, the reports will exclude the non-covered.cpp file.
target_code_coverage(theExe EXCLUDE non_covered.cpp) # As an executable target, the reports will exclude the non_covered.cpp file.
```
#### Example 3: Target added to the 'ccov' and 'ccov-all' targets
@ -168,6 +150,40 @@ add_executable(theExe main.cpp non_covered.cpp)
target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder.
```
## AFL Fuzzing Instrumentation [`afl-fuzzing.cmake`](afl-fuzzing.cmake)
> American fuzzy lop is a security-oriented fuzzer that employs a novel type of compile-time instrumentation and genetic algorithms to automatically discover clean, interesting test cases that trigger new internal states in the targeted binary. This substantially improves the functional coverage for the fuzzed code. The compact synthesized corpora produced by the tool are also useful for seeding other, more labor- or resource-intensive testing regimes down the road.
>
> [american fuzzy lop](https://lcamtuf.coredump.cx/afl/)
NOTE: This actually works based off the still-developed daughter project [AFL++](https://aflplus.plus/).
### Usage
To enable the use of AFL instrumentation, this file needs to be included into the CMake scripts at any point *before* any of the compilers are setup by CMake, typically at/before the first call to project(), or any part before compiler detection/validation occurs. This is since CMake does not support changing the compiler after it has been set:
```
cmake_minimum_required(VERSION 3.4)
include(cmake/afl-fuzzing.cmake)
project(Example C CXX)
```
Using `-DAFL=ON` will search for and switch to the AFL++ compiler wrappers that will instrument builds, or error if it cannot.
Using `-DAFL_MODE=<MODE>` will attempt to use the specified instrumentation type, see [here](https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/fuzzing_in_depth.md). Options are:
- LTO
- LLVM
- GCC-PLUGIN
- CLANG
- GCC
Using `-DAFL_ENV_OPTIONS=<...;...>` allows adding any number of AFL++'s instrumentation enabled via environment variables, and these will be prefixed to the build calls (see `afl-cc -hh`).
As an example, a CMake configuration such as this:
```cmake .. -DAFL_MODE=LTO -DAFL_ENV_OPTIONS=AFL_LLVM_THREADSAFE_INST=1;AFL_LLVM_LAF_ALL=1```
would result in build commands such as this:
```AFL_LLVM_THREADSAFE_INST=1 AFL_LLVM_LAF_ALL=1 afl-clang-lto --afl-lto <...>```
## Compiler Options [`compiler-options.cmake`](compiler-options.cmake)
Allows for easy use of some pre-made compiler options for the major compilers.
@ -205,75 +221,62 @@ The name to give the doc target. (Default: doc-${PROJECT_NAME})
#### OUTPUT_DIR *STR*
The directory to place the generated output
## Doxygen [`doxygen.cmake`](doxygen.cmake)
Builds doxygen documentation with a default 'Doxyfile.in' or with a specified one, and can make the results installable (under the `doc` install target)
This can only be used once per project, as each target generated is as `doc-${PROJECT_NAME}` unless TARGET_NAME is specified.
### Optional Arguments
#### ADD_TO_DOC
If specified, adds this generated target to be a dependency of the more general `doc` target.
#### INSTALLABLE
Adds the generated documentation to the generic `install` target, under the `documentation` installation group.
#### PROCESS_DOXYFILE
If set, then will process the found Doxyfile through the CMAKE `configure_file` function for macro replacements before using it. (@ONLY)
#### TARGET_NAME *STR*
The name to give the doc target. (Default: doc-${PROJECT_NAME})
#### OUTPUT_DIR *STR*
The directory to place the generated output. (Default: ${CMAKE_CURRENT_BINARY_DIR}/doc)
#### INSTALL_PATH *STR*
The path to install the documenttation under. (if not specified, defaults to 'share/${PROJECT_NAME})
#### DOXYFILE_PATH *STR*
The given doxygen file to use/process. (Defaults to'${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile')
## Prepare the Catch Test Framework [`prepare_catch.cmake`](prepare_catch.cmake)
The included `prepare_catch` function contained within attempts to add the infrastructure necessary for automatically adding C/C++ tests using the Catch2 library, including either an interface or pre-compiled 'catch' target library.
It first attempts to find the header on the local machine, and failing that, clones the single header variant for use. It does make the determination between pre-C++11 and will use Catch1.X rather than Catch2 (when cloned), automatically or forced.. Adds a subdirectory of tests/ if it exists from the macro's calling location.
### Optional Arguments
#### COMPILED_CATCH
If this option is specified, then generates the 'catch' target as a library with catch already pre-compiled as part of the library. Otherwise acts just an interface library for the header location.
#### CATCH1
Force the use of Catch1.X, rather than auto-detecting the C++ version in use.
#### CLONE
Force cloning of Catch, rather than attempting to use a locally-found variant.
## Tools [`tools.cmake`](tools.cmake)
The three tools in this are used via two provided functions each, for example for clang-tidy:
```
add_executable(big_test)
clang_tidy()
# Sources provided here are run with clang-tidy with no options
add_executable(test2 main2.cpp)
target_sources(big_test test2.c test2.cpp)
clang_tidy(-header-filter='${CMAKE_SOURCE_DIR}/*')
# Sources provided here are run with clang-tidy with the header-filter options provided to it from above
add_execuable(test1 main1.cpp)
target_sources(big_test test1.c test1.cpp)
reset_clang_tidy()
# Sources provided here are not run with clang-tidy at all
add_executable(test3 main3.cpp)
target_sources(big_test test3.c test3.cpp)
clang_tidy()
# Sources provided here are run with clang-tidy with no options
add_executable(test4 main4.cpp)
target_sources(big_test test4.c test4.cpp)
```
### clang-tidy
> clang-tidy is a clang-based C++ “linter” tool. Its purpose is to provide an extensible framework for diagnosing and fixing typical programming errors, like style violations, interface misuse, or bugs that can be deduced via static analysis. clang-tidy is modular and provides a convenient interface for writing new checks.
>
> [clang-tidy page](https://clang.llvm.org/extra/clang-tidy/)
> [clang-tidy](https://clang.llvm.org/extra/clang-tidy/)
When detected, [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) can be enabled by using the option of `-DCLANG_TIDY=ON`, as it is disabled by default.
To use, add the `clang_tidy()` function, with the arguments being the options to pass to the clang tidy program, such as '-checks=*'.
To use, add the `clang_tidy()` macro, with the arguments being the options passed to the clang-tidy call in the form of `clang-tidy ${ARGS}`. The settings used with clang-tidy can be changed by calling `clang_tidy()` macro again. It can be turned off by calling the `reset_clang_tidy()` macro.
### include-what-you-use
This tool helps to organize headers for all files encompass all items being used in that file, without accidentally relying upon headers deep down a chain of other headers. This is disabled by default, and can be enabled via have the program installed and adding `-DIWYU=ON`.
> "Include what you use" means this: for every symbol (type, function variable, or macro) that you use in foo.cc, either foo.cc or foo.h should #include a .h file that exports the declaration of that symbol. The include-what-you-use tool is a program that can be built with the clang libraries in order to analyze #includes of source files to find include-what-you-use violations, and suggest fixes for them.
>
> The main goal of include-what-you-use is to remove superfluous #includes. It does this both by figuring out what #includes are not actually needed for this file (for both .cc and .h files), and replacing #includes with forward-declares when possible.
>
> [include-what-you-use](https://include-what-you-use.org/)
To use, add the `include_what_you_use()` function, with the arguments being the options to pass to the program.
To use, add the `include_what_you_use()` macro, with the arguments being the options passed to the include_what_you_use call in the form of `include-what-you-use ${ARGS}`. The settings used with include-what-you-use can be changed by calling `include_what_you_use()` macro again. It can be turned off by calling the `reset_include_what_you_use()` macro.
### cppcheck
This tool is another static analyzer in the vein of clang-tidy, which focuses on having no false positives. This is by default disabled, and can be enabled via have the program installed and adding `-DCPPCHECK=ON`.
> Cppcheck is a static analysis tool for C/C++ code. It provides unique code analysis to detect bugs and focuses on detecting undefined behaviour and dangerous coding constructs. The goal is to have very few false positives. Cppcheck is designed to be able to analyze your C/C++ code even if it has non-standard syntax (common in embedded projects).
>
> [cppcheck](http://cppcheck.net/)
To use, add the `cppcheck()` function, with the arguments being the options to pass to the program.
To use, add the `cppcheck()` macro, with the arguments being the options passed to the cppcheck call in the form of `cppcheck ${ARGS}`. The settings used with iwyu can be changed by calling `cppcheck()` macro again. It can be turned off by calling the `reset_cppcheck()` macro.
## Formatting [`formatting.cmake`](formatting.cmake)
@ -305,4 +308,3 @@ file(GLOB_RECURSE CMAKE_FILES
cmake_format(TARGET_NAME ${CMAKE_FILES})
```

View File

@ -0,0 +1,108 @@
#
# Copyright (C) 2022 by George Cave - gcave@stablecoder.ca
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
# USAGE: To enable the use of AFL instrumentation, this file needs to be
# included into the CMake scripts at any point *before* any of the compilers are
# setup by CMake, typically at/before the first call to project(), or any part
# before compiler detection/validation occurs.
#
# This is since CMake does not support changing the compiler after it has been
# set.
#
# For example for CMakeLists.txt:
# ~~~
# cmake_minimum_required(VERSION 3.15)
# include(cmake/afl-fuzzing.cmake)
# project(FoE-Engine C CXX)
# ~~~
# And then configuring CMake with: `cmake .. -DAFL_MODE=LTO
# -DAFL_ENV_OPTIONS=AFL_LLVM_THREADSAFE_INST=1;AFL_LLVM_LAF_ALL=1`
#
# Would setup the AFL compiler to use the LTO mode (afl-clang-lto), and prefix
# any build calls to have the two given environment settings, ie:
# `AFL_LLVM_THREADSAFE_INST=1 AFL_LLVM_LAF_ALL=1 afl-clang-lto <...>`
#
# NOTE: If using multiple ENV_OPTIONS, delimit via semi-colons and it will be
# separated correctly.
# Options
option(AFL "Switch to using an AFL compiler" OFF)
set(AFL_MODE
""
CACHE
STRING
"Use a specific AFL instrumentation mode: LTO, LLVM, GCC-PLUGIN, CLANG, GCC"
)
set(AFL_ENV_OPTIONS
""
CACHE STRING
"Add environmental settings to build calls (check `afl-cc -hh`)")
# Sets up for AFL fuzzing by detecting finding and using AFL compilers and
# setting a few flags and environmental build flags as requested.
if(AFL)
find_program(AFL_C_COMPILER afl-cc)
find_program(AFL_CXX_COMPILER afl-c++)
if(AFL_C_COMPILER AND AFL_CXX_COMPILER)
if((CMAKE_C_COMPILER AND NOT CMAKE_C_COMPILER STREQUAL AFL_C_COMPILER)
OR (CMAKE_CXX_COMPILER AND NOT CMAKE_CXX_COMPILER STREQUAL
AFL_CXX_COMPILER))
# CMake doesn't support changing compilers after they've been set
message(
FATAL_ERROR
"Cannot change to AFL compilers after they have been previously set. Clear the cache, reconfigure and ensure setup_afl is called before the first C or CXX compiler is set, typically before the first project() call."
)
else()
# Set the AFL compiler
message(STATUS "Changed to AFL compiler")
set(CMAKE_C_COMPILER ${AFL_C_COMPILER})
set(CMAKE_CXX_COMPILER ${AFL_CXX_COMPILER})
# Set a specific AFL mode for both compile and link stages
if(AFL_MODE MATCHES "[Ll][Tt][Oo]")
message(STATUS "Set AFL to Clang-LTO mode")
add_compile_options(--afl-lto)
add_link_options(--afl-lto)
elseif(AFL_MODE MATCHES "[Ll][Ll][Vv][Mm]")
message(STATUS "Set AFL to Clang-LLVM mode")
add_compile_options(--afl-llvm)
add_link_options(--afl-llvm)
elseif(AFL_MODE MATCHES "[Gg][Cc][Cc][-_][Pp][Ll][Uu][Gg][Ii][Nn]")
message(STATUS "Set AFL to GCC-Plugin mode")
add_compile_options(--afl-gcc-plugin)
add_link_options(--afl-gcc-plugin)
elseif(AFL_MODE MATCHES "[Ll][Tt][Oo]")
message(STATUS "Set AFL to Clang mode")
add_compile_options(--afl-clang)
add_link_options(--afl-clang)
elseif(AFL_MODE MATCHES "[Ll][Tt][Oo]")
message(STATUS "Set AFL to GCC mode")
add_compile_options(--afl-gcc)
add_link_options(--afl-gcc)
endif()
# Add specified environment options
if(AFL_ENV_OPTIONS)
set(CMAKE_C_COMPILER_LAUNCHER ${CMAKE_C_COMPILER_LAUNCHER}
${AFL_ENV_OPTIONS})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CMAKE_CXX_COMPILER_LAUNCHER}
${AFL_ENV_OPTIONS})
endif()
endif()
else()
message(FATAL_ERROR "Usable AFL compiler was not found!")
endif()
endif()

View File

@ -1,5 +1,5 @@
#
# Copyright (C) 2018 by George Cave - gcave@stablecoder.ca
# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
@ -80,36 +80,44 @@ option(
OFF)
# Programs
if(WIN32)
find_program(LLVM_COV_PATH llvm-cov PATHS ENV VS2019INSTALLDIR PATH_SUFFIXES "VC/Tools/Llvm/x64/bin")
find_program(LLVM_PROFDATA_PATH llvm-profdata PATHS ENV VS2019INSTALLDIR PATH_SUFFIXES "VC/Tools/Llvm/x64/bin")
find_program(LCOV_PATH lcov PATHS ENV VS2019INSTALLDIR PATH_SUFFIXES "VC/Tools/Llvm/x64/bin")
find_program(GENHTML_PATH genhtml PATHS ENV VS2019INSTALLDIR PATH_SUFFIXES "VC/Tools/Llvm/x64/bin")
else()
find_program(LLVM_COV_PATH llvm-cov)
find_program(LLVM_PROFDATA_PATH llvm-profdata)
find_program(LCOV_PATH lcov)
find_program(GENHTML_PATH genhtml)
endif()
find_program(LLVM_COV_PATH llvm-cov)
find_program(LLVM_PROFDATA_PATH llvm-profdata)
find_program(LCOV_PATH lcov)
find_program(GENHTML_PATH genhtml)
# Hide behind the 'advanced' mode flag for GUI/ccmake
mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH)
# Variables
set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov)
set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1)
# Common initialization/checks
if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED)
set(CODE_COVERAGE_ADDED ON)
# Common Targets
add_custom_target(
ccov-preprocessing
COMMAND ${CMAKE_COMMAND} -E make_directory
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}
DEPENDS ccov-clean)
file(MAKE_DIRECTORY ${CMAKE_COVERAGE_OUTPUT_DIRECTORY})
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang")
# Messages
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
message(STATUS "Building with llvm Code Coverage Tools")
if(CMAKE_C_COMPILER_ID MATCHES "AppleClang" OR CMAKE_CXX_COMPILER_ID
MATCHES "AppleClang")
# When on macOS and using the Apple-provided toolchain, use the
# XCode-provided llvm toolchain via `xcrun`
message(
STATUS
"Building with XCode-provided llvm code coverage tools (via `xcrun`)")
set(LLVM_COV_PATH xcrun llvm-cov)
set(LLVM_PROFDATA_PATH xcrun llvm-profdata)
else()
# Use the regular llvm toolchain
message(STATUS "Building with llvm code coverage tools")
endif()
if(NOT LLVM_COV_PATH)
message(FATAL_ERROR "llvm-cov not found! Aborting.")
else()
@ -128,10 +136,21 @@ if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED)
endif()
# Targets
add_custom_target(
ccov-clean
COMMAND rm -f ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
COMMAND rm -f ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list)
if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
add_custom_target(
ccov-clean
COMMAND ${CMAKE_COMMAND} -E remove -f
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
COMMAND ${CMAKE_COMMAND} -E remove -f
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list)
else()
add_custom_target(
ccov-clean
COMMAND ${CMAKE_COMMAND} -E rm -f
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
COMMAND ${CMAKE_COMMAND} -E rm -f
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list)
endif()
# Used to get the shared object file list before doing the main all-
# processing
@ -189,44 +208,74 @@ endif()
# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter.
# ccov-${TARGET_NAME} : Generates HTML code coverage report.
# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information.
# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file.
# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information.
# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report.
# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line.
# ccov-all-export : Exports the coverage report to a JSON file.
#
# Required:
# TARGET_NAME - Name of the target to generate code coverage for.
# Optional:
# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE.
# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE.
# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects)
# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets.
# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets.
# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory
# EXCLUDE <REGEX_PATTERNS> - Excludes files of the patterns provided from coverage. **These do not copy to the 'all' targets.**
# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`.
# EXCLUDE <PATTERNS> - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.**
# OBJECTS <TARGETS> - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output
# PRE_ARGS <ARGUMENTS> - For executables ONLY, prefixes given arguments to the associated ccov-* executable call ($<PRE_ARGS> ccov-*)
# ARGS <ARGUMENTS> - For executables ONLY, appends the given arguments to the associated ccov-* executable call (ccov-* $<ARGS>)
# ~~~
function(target_code_coverage TARGET_NAME)
# Argument parsing
set(options AUTO ALL EXTERNAL)
set(multi_value_keywords EXCLUDE OBJECTS)
cmake_parse_arguments(target_code_coverage "${options}" ""
"${multi_value_keywords}" ${ARGN})
set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN)
set(single_value_keywords COVERAGE_TARGET_NAME)
set(multi_value_keywords EXCLUDE OBJECTS PRE_ARGS ARGS)
cmake_parse_arguments(
target_code_coverage "${options}" "${single_value_keywords}"
"${multi_value_keywords}" ${ARGN})
# Set the visibility of target functions to PUBLIC, INTERFACE or default to
# PRIVATE.
if(target_code_coverage_PUBLIC)
set(TARGET_VISIBILITY PUBLIC)
set(TARGET_LINK_VISIBILITY PUBLIC)
elseif(target_code_coverage_INTERFACE)
set(TARGET_VISIBILITY INTERFACE)
set(TARGET_LINK_VISIBILITY INTERFACE)
elseif(target_code_coverage_PLAIN)
set(TARGET_VISIBILITY PUBLIC)
set(TARGET_LINK_VISIBILITY)
else()
set(TARGET_VISIBILITY PRIVATE)
set(TARGET_LINK_VISIBILITY PRIVATE)
endif()
if(NOT target_code_coverage_COVERAGE_TARGET_NAME)
# If a specific name was given, use that instead.
set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME})
endif()
if(CODE_COVERAGE)
# Add code coverage instrumentation to the target's linker command
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang")
target_compile_options(${TARGET_NAME} PRIVATE -fprofile-instr-generate
-fcoverage-mapping --coverage)
set_property(
TARGET ${TARGET_NAME}
APPEND_STRING
PROPERTY LINK_FLAGS "-fprofile-instr-generate ")
set_property(
TARGET ${TARGET_NAME}
APPEND_STRING
PROPERTY LINK_FLAGS "-fcoverage-mapping ")
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
target_compile_options(${TARGET_NAME} PRIVATE -fprofile-arcs
-ftest-coverage --coverage)
target_link_libraries(${TARGET_NAME} PRIVATE gcov)
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR
CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY}
-fprofile-instr-generate -fcoverage-mapping)
target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY}
-fprofile-instr-generate -fcoverage-mapping)
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
"GNU")
target_compile_options(
${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs -ftest-coverage
$<$<COMPILE_LANGUAGE:CXX>:-fno-elide-constructors> -fno-default-inline)
target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov)
endif()
# Targets
@ -234,12 +283,16 @@ function(target_code_coverage TARGET_NAME)
# Add shared library to processing for 'all' targets
if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL)
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang")
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR
CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
add_custom_target(
ccov-run-${TARGET_NAME}
COMMAND echo "-object=$<TARGET_FILE:${TARGET_NAME}>" >>
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
DEPENDS ccov-preprocessing ${TARGET_NAME})
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${CMAKE_COMMAND} -E echo "-object=$<TARGET_FILE:${TARGET_NAME}>" >>
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
DEPENDS ${TARGET_NAME})
if(NOT TARGET ccov-libs)
message(
@ -248,13 +301,17 @@ function(target_code_coverage TARGET_NAME)
)
endif()
add_dependencies(ccov-libs ccov-run-${TARGET_NAME})
add_dependencies(ccov-libs
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
endif()
endif()
# For executables add targets to run and produce output
if(target_type STREQUAL "EXECUTABLE")
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?Cc]lang")
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR
CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
# If there are shared objects to also work with, generate the string to
# add them here
@ -268,23 +325,34 @@ function(target_code_coverage TARGET_NAME)
endif()
endforeach()
# Run the executable, generating raw profile data
# Run the executable, generating raw profile data Make the run data
# available for further processing. Separated to allow Windows to run
# this target serially.
add_custom_target(
ccov-run-${TARGET_NAME}
COMMAND LLVM_PROFILE_FILE=${TARGET_NAME}.profraw
$<TARGET_FILE:${TARGET_NAME}>
COMMAND echo "-object=$<TARGET_FILE:${TARGET_NAME}>" >>
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
COMMAND echo "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}.profraw " >>
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list
DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME})
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${CMAKE_COMMAND} -E env ${CMAKE_CROSSCOMPILING_EMULATOR}
${target_code_coverage_PRE_ARGS}
LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw
$<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
COMMAND
${CMAKE_COMMAND} -E echo "-object=$<TARGET_FILE:${TARGET_NAME}>"
${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
COMMAND
${CMAKE_COMMAND} -E echo
"${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw"
>> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list
JOB_POOL ccov_serial_pool
DEPENDS ccov-libs ${TARGET_NAME})
# Merge the generated profile data so llvm-cov can process it
add_custom_target(
ccov-processing-${TARGET_NAME}
COMMAND ${LLVM_PROFDATA_PATH} merge -sparse ${TARGET_NAME}.profraw -o
${TARGET_NAME}.profdata
DEPENDS ccov-run-${TARGET_NAME})
ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_PROFDATA_PATH} merge -sparse
${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o
${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
# Ignore regex only works on LLVM >= 7
if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0")
@ -296,40 +364,57 @@ function(target_code_coverage TARGET_NAME)
# Print out details of the coverage information to the command line
add_custom_target(
ccov-show-${TARGET_NAME}
ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_COV_PATH} show $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
-instr-profile=${TARGET_NAME}.profdata -show-line-counts-or-regions
${EXCLUDE_REGEX}
DEPENDS ccov-processing-${TARGET_NAME})
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
-show-line-counts-or-regions ${EXCLUDE_REGEX}
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
# Print out a summary of the coverage information to the command line
add_custom_target(
ccov-report-${TARGET_NAME}
COMMAND ${LLVM_COV_PATH} report $<TARGET_FILE:${TARGET_NAME}>
${SO_OBJECTS} -instr-profile=${TARGET_NAME}.profdata
${EXCLUDE_REGEX}
DEPENDS ccov-processing-${TARGET_NAME})
ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_COV_PATH} report $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
${EXCLUDE_REGEX}
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
# Export coverage information so continuous integration tools (e.g.
# Jenkins) can consume it
add_custom_target(
ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_COV_PATH} export $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
-format="text" ${EXCLUDE_REGEX} >
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
# Generates HTML output of the coverage information for perusal
add_custom_target(
ccov-${TARGET_NAME}
ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_COV_PATH} show $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
-instr-profile=${TARGET_NAME}.profdata -show-line-counts-or-regions
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME}
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
-show-line-counts-or-regions
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}
-format="html" ${EXCLUDE_REGEX}
DEPENDS ccov-processing-${TARGET_NAME})
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
"GNU")
set(COVERAGE_INFO
"${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME}.info")
"${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info"
)
# Run the executable, generating coverage information
add_custom_target(
ccov-run-${TARGET_NAME}
COMMAND $<TARGET_FILE:${TARGET_NAME}>
DEPENDS ccov-preprocessing ${TARGET_NAME})
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS}
$<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
DEPENDS ${TARGET_NAME})
# Generate exclusion string for use
foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE})
@ -349,33 +434,52 @@ function(target_code_coverage TARGET_NAME)
endif()
# Capture coverage data
add_custom_target(
ccov-capture-${TARGET_NAME}
COMMAND ${CMAKE_COMMAND} -E remove ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
COMMAND $<TARGET_FILE:${TARGET_NAME}>
COMMAND
${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ccov-preprocessing ${TARGET_NAME})
if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
add_custom_target(
ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
COMMAND
${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS}
$<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
COMMAND
${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ${TARGET_NAME})
else()
add_custom_target(
ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
COMMAND
${CMAKE_CROSSCOMPILING_EMULATOR} ${target_code_coverage_PRE_ARGS}
$<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
COMMAND
${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ${TARGET_NAME})
endif()
# Generates HTML output of the coverage information for perusal
add_custom_target(
ccov-${TARGET_NAME}
COMMAND ${GENHTML_PATH} -o
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME}
${COVERAGE_INFO}
DEPENDS ccov-capture-${TARGET_NAME})
ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${GENHTML_PATH} -o
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}
${COVERAGE_INFO}
DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME})
endif()
add_custom_command(
TARGET ccov-${TARGET_NAME}
TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
POST_BUILD
COMMAND ;
COMMENT
"Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME}/index.html in your browser to view the coverage report."
"Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report."
)
# AUTO
@ -383,13 +487,16 @@ function(target_code_coverage TARGET_NAME)
if(NOT TARGET ccov)
add_custom_target(ccov)
endif()
add_dependencies(ccov ccov-${TARGET_NAME})
add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME})
if(NOT CMAKE_COMPILER_IS_GNUCXX)
if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID
MATCHES "GNU")
if(NOT TARGET ccov-report)
add_custom_target(ccov-report)
endif()
add_dependencies(ccov-report ccov-report-${TARGET_NAME})
add_dependencies(
ccov-report
ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME})
endif()
endif()
@ -402,7 +509,8 @@ function(target_code_coverage TARGET_NAME)
)
endif()
add_dependencies(ccov-all-processing ccov-run-${TARGET_NAME})
add_dependencies(ccov-all-processing
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
endif()
endif()
endif()
@ -412,12 +520,20 @@ endfunction()
# any subdirectories. To add coverage instrumentation to only specific targets,
# use `target_code_coverage`.
function(add_code_coverage)
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang")
add_compile_options(-fprofile-instr-generate -fcoverage-mapping --coverage)
add_link_options(-fprofile-instr-generate -fcoverage-mapping --coverage)
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compile_options(-fprofile-arcs -ftest-coverage --coverage)
link_libraries(gcov)
if(CODE_COVERAGE)
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR
CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
add_link_options(-fprofile-instr-generate -fcoverage-mapping)
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
"GNU")
add_compile_options(
-fprofile-arcs -ftest-coverage
$<$<COMPILE_LANGUAGE:CXX>:-fno-elide-constructors> -fno-default-inline)
link_libraries(gcov)
endif()
endif()
endfunction()
@ -428,7 +544,7 @@ endfunction()
# use with coverage dashboards (e.g. codecov.io, coveralls).
# ~~~
# Optional:
# EXCLUDE <REGEX_PATTERNS> - Excludes files of the regex patterns provided from coverage.
# EXCLUDE <PATTERNS> - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex!
# ~~~
function(add_code_coverage_all_targets)
# Argument parsing
@ -437,15 +553,28 @@ function(add_code_coverage_all_targets)
"${multi_value_keywords}" ${ARGN})
if(CODE_COVERAGE)
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang" OR CMAKE_CXX_COMPILER_ID MATCHES "[Cc]lang")
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM" OR
CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" OR
CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
# Merge the profile data for all of the run executables
add_custom_target(
ccov-all-processing
COMMAND
${LLVM_PROFDATA_PATH} merge -o
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`)
if(WIN32)
add_custom_target(
ccov-all-processing
COMMAND
powershell -Command $$FILELIST = Get-Content
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe
merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-sparse $$FILELIST)
else()
add_custom_target(
ccov-all-processing
COMMAND
${LLVM_PROFDATA_PATH} merge -o
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`)
endif()
# Regex exclude only available for LLVM >= 7
if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0")
@ -456,38 +585,77 @@ function(add_code_coverage_all_targets)
endif()
# Print summary of the code coverage information to the command line
add_custom_target(
ccov-all-report
COMMAND
${LLVM_COV_PATH} report `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
if(WIN32)
add_custom_target(
ccov-all-report
COMMAND
powershell -Command $$FILELIST = Get-Content
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe
report $$FILELIST
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
else()
add_custom_target(
ccov-all-report
COMMAND
${LLVM_COV_PATH} report `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
endif()
# Export coverage information so continuous integration tools (e.g.
# Jenkins) can consume it
add_custom_target(
ccov-all-export
COMMAND
${LLVM_COV_PATH} export `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-format="text" ${EXCLUDE_REGEX} >
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json
DEPENDS ccov-all-processing)
if(WIN32)
add_custom_target(
ccov-all-export
COMMAND
powershell -Command $$FILELIST = Get-Content
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe
export $$FILELIST
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-format="text" ${EXCLUDE_REGEX} >
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json
DEPENDS ccov-all-processing)
else()
add_custom_target(
ccov-all-export
COMMAND
${LLVM_COV_PATH} export `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-format="text" ${EXCLUDE_REGEX} >
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json
DEPENDS ccov-all-processing)
endif()
# Generate HTML output of all added targets for perusal
add_custom_target(
ccov-all
COMMAND
${LLVM_COV_PATH} show `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-show-line-counts-or-regions
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
-format="html" ${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
if(WIN32)
add_custom_target(
ccov-all
COMMAND
powershell -Command $$FILELIST = Get-Content
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show
$$FILELIST
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-show-line-counts-or-regions
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
-format="html" ${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
else()
add_custom_target(
ccov-all
COMMAND
${LLVM_COV_PATH} show `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-show-line-counts-or-regions
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
-format="html" ${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
endif()
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info")
@ -496,6 +664,7 @@ function(add_code_coverage_all_targets)
add_custom_target(ccov-all-processing COMMAND ;)
# Exclusion regex string creation
set(EXCLUDE_REGEX)
foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE})
set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO}
'${EXCLUDE_ITEM}')
@ -509,19 +678,29 @@ function(add_code_coverage_all_targets)
endif()
# Capture coverage data
add_custom_target(
ccov-all-capture
COMMAND ${CMAKE_COMMAND} -E remove ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture
--output-file ${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ccov-all-processing)
if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
add_custom_target(
ccov-all-capture
COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture
--output-file ${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ccov-all-processing)
else()
add_custom_target(
ccov-all-capture
COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture
--output-file ${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ccov-all-processing)
endif()
# Generates HTML output of all targets for perusal
add_custom_target(
ccov-all
COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
${COVERAGE_INFO}
${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR}
DEPENDS ccov-all-capture)
endif()

View File

@ -0,0 +1,107 @@
#
# Copyright (C) 2019 by George Cave - gcave@stablecoder.ca
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
find_program(DOT_EXE "dot")
mark_as_advanced(FORCE DOT_EXE)
if(DOT_EXE)
message(STATUS "dot found: ${DOT_EXE}")
else()
message(STATUS "dot not found!")
endif()
if(NOT DOT_EXE)
option(
BUILD_DEP_GRAPH
"Builds a visual representation of the dependencies of that included targets"
OFF)
else()
option(
BUILD_DEP_GRAPH
"Builds a visual representation of the dependencies of that included targets"
ON)
endif()
# Builds a dependency graph of the active code targets using the `dot`
# application
#
# This can only be used once per project, as each target generated is as
# `doc-${PROJECT_NAME}` unless TARGET_NAME is specified.
# ~~~
# Required Arguments:
# OUTPUT_TYPE
# This is the output type, which doubles as the output file type, such as pdf, png.
# This can be whatever the `dot` application allows.
#
# Options Arguments:
# ADD_TO_DEP_GRAPH
# If specified, add this generated target to be a dependency of the more general
# `dep-graph` target.
#
# TARGET_NAME <str>
# The name to give the doc target. (Default: dep-graph-${PROJECT_NAME})
#
# OUTPUT_DIR <str>
# The directory to place the generated output
# ~~~
function(gen_dep_graph OUTPUT_TYPE)
set(OPTIONS ADD_TO_DEP_GRAPH)
set(SINGLE_VALUE_KEYWORDS TARGET_NAME OUTPUT_DIR)
set(MULTI_VALUE_KEYWORDS)
cmake_parse_arguments(gen_dep_graph "${OPTIONS}" "${SINGLE_VALUE_KEYWORDS}"
"${MULTI_VALUE_KEYWORDS}" ${ARGN})
if(BUILD_DEP_GRAPH)
if(NOT DOT_EXE)
message(FATAL_ERROR "`dot` is needed to build the dependency graph.")
endif()
if(gen_dep_graph_TARGET_NAME)
set(TARGET_NAME ${gen_dep_graph_TARGET_NAME})
else()
set(TARGET_NAME dep-graph-${PROJECT_NAME})
endif()
if(gen_dep_graph_OUTPUT_DIR)
set(OUT_DIR ${gen_dep_graph_OUTPUT_DIR})
else()
set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
endif()
add_custom_target(
${TARGET_NAME}
COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR}
--graphviz=${CMAKE_CURRENT_BINARY_DIR}/graphviz/${TARGET_NAME}.dot
COMMAND
${DOT_EXE} -T${OUTPUT_TYPE}
${CMAKE_CURRENT_BINARY_DIR}/graphviz/${TARGET_NAME}.dot -o
${OUT_DIR}/${TARGET_NAME}.${OUTPUT_TYPE})
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ;
COMMENT
"Dependency graph for ${TARGET_NAME} generated and located at ${OUT_DIR}/${TARGET_NAME}.${OUTPUT_TYPE}"
)
if(gen_dep_graph_ADD_TO_DEP_GRAPH)
if(NOT TARGET dep-graph)
add_custom_target(dep-graph)
endif()
add_dependencies(dep-graph ${TARGET_NAME})
endif()
endif()
endfunction()

View File

@ -47,10 +47,7 @@ function(clang_format TARGET_NAME)
# If the item is a target, then we'll attempt to grab the associated
# source files from it.
get_target_property(_TARGET_TYPE ${item} TYPE)
if(NOT
_TARGET_TYPE
STREQUAL
"INTERFACE_LIBRARY")
if(NOT _TARGET_TYPE STREQUAL "INTERFACE_LIBRARY")
get_property(
_TEMP
TARGET ${item}
@ -72,21 +69,68 @@ function(clang_format TARGET_NAME)
# Make the target
if(FORMAT_FILES)
if(TARGET ${TARGET_NAME})
message(
ERROR
"Cannot create clang-format target '${TARGET_NAME}', already exists.")
else()
add_custom_target(${TARGET_NAME} COMMAND ${CLANG_FORMAT_EXE} -i -style=file ${FORMAT_FILES})
add_custom_target(${TARGET_NAME} COMMAND ${CLANG_FORMAT_EXE} -i
-style=file ${FORMAT_FILES})
if(NOT TARGET format)
add_custom_target(format)
endif()
add_dependencies(format ${TARGET_NAME})
if(NOT TARGET format)
add_custom_target(format)
endif()
add_dependencies(format ${TARGET_NAME})
endif()
endif()
endfunction()
#
# cmake-format
#
find_program(CMAKE_FORMAT_EXE "cmake-format")
mark_as_advanced(FORCE CMAKE_FORMAT_EXE)
if(CMAKE_FORMAT_EXE)
message(STATUS "cmake-format found: ${CMAKE_FORMAT_EXE}")
else()
message(STATUS "cmake-format not found!")
endif()
# When called, this function will call 'cmake-format' program on all listed
# files (if both the program and the files exist and are found)
# ~~~
# Required:
# TARGET_NAME - The name of the target to create.
#
# Optional:
# ARGN - Any arguments passed in will be considered as 'files' to perform the
# formatting on. Any items that are not files will be ignored. Both relative and
# absolute paths are accepted.
# ~~~
function(cmake_format TARGET_NAME)
if(CMAKE_FORMAT_EXE)
set(FORMAT_FILES)
# Determine files that exist
foreach(iter IN LISTS ARGN)
if(EXISTS ${iter})
set(FORMAT_FILES ${FORMAT_FILES} ${iter})
elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${iter})
set(FORMAT_FILES ${FORMAT_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/${iter})
endif()
endforeach()
# Generate target
if(FORMAT_FILES)
if(TARGET ${TARGET_NAME})
message(
ERROR
"Cannot create cmake-format target '${TARGET_NAME}', already exists.")
else()
add_custom_target(${TARGET_NAME} COMMAND ${CMAKE_FORMAT_EXE} -i
${FORMAT_FILES})
if(NOT TARGET cmake-format)
add_custom_target(cmake-format)
endif()
add_dependencies(cmake-format ${TARGET_NAME})
endif()
endif()
endif()
endfunction()

View File

@ -1,5 +1,5 @@
#
# Copyright (C) 2018 by George Cave - gcave@stablecoder.ca
# Copyright (C) 2018-2022 by George Cave - gcave@stablecoder.ca
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
@ -13,11 +13,13 @@
# License for the specific language governing permissions and limitations under
# the License.
include(CheckCXXSourceCompiles)
set(USE_SANITIZER
""
CACHE
STRING
"Compile with a sanitizer. Options are: Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined'"
"Compile with a sanitizer. Options are: Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined', CFI"
)
function(append value)
@ -28,62 +30,164 @@ function(append value)
endforeach(variable)
endfunction()
message(STATUS "USE_SANITIZER=${USE_SANITIZER}, CMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}")
function(append_quoteless value)
foreach(variable ${ARGN})
set(${variable}
${${variable}} ${value}
PARENT_SCOPE)
endforeach(variable)
endfunction()
function(test_san_flags return_var flags)
set(QUIET_BACKUP ${CMAKE_REQUIRED_QUIET})
set(CMAKE_REQUIRED_QUIET TRUE)
unset(${return_var} CACHE)
set(FLAGS_BACKUP ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "${flags}")
check_cxx_source_compiles("int main() { return 0; }" ${return_var})
set(CMAKE_REQUIRED_FLAGS "${FLAGS_BACKUP}")
set(CMAKE_REQUIRED_QUIET "${QUIET_BACKUP}")
endfunction()
if(USE_SANITIZER)
if(CMAKE_C_COMPILER_ID MATCHES "IntelLLVM" OR CMAKE_C_COMPILER_ID MATCHES "[Cc]lang")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
unset(SANITIZER_SELECTED_FLAGS)
if(UNIX)
append("-fno-omit-frame-pointer" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
message(STATUS "Building with sanitize, base flags=${CMAKE_C_SANITIZER_FLAGS}")
append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
if(uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG")
append("-O1" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
append("-O1" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()
if(USE_SANITIZER MATCHES "([Aa]ddress);([Uu]ndefined)"
OR USE_SANITIZER MATCHES "([Uu]ndefined);([Aa]ddress)")
message(STATUS "Building with Address, Undefined sanitizers")
append("-fsanitize=address,undefined" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
set(MEMCHECK_TYPE AddressSanitizer)
elseif(USE_SANITIZER MATCHES "([Aa]ddress)")
if(USE_SANITIZER MATCHES "([Aa]ddress)")
# Optional: -fno-optimize-sibling-calls -fsanitize-address-use-after-scope
message(STATUS "Building with Address sanitizer")
append("-fsanitize=address" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
set(MEMCHECK_TYPE AddressSanitizer)
elseif(USE_SANITIZER MATCHES "([Mm]emory([Ww]ith[Oo]rigins)?)")
# Optional: -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2
append("-fsanitize=memory" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
if(USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)")
message(STATUS "Building with MemoryWithOrigins sanitizer")
append("-fsanitize-memory-track-origins" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
message(STATUS "Testing with Address sanitizer")
set(SANITIZER_ADDR_FLAG "-fsanitize=address")
test_san_flags(SANITIZER_ADDR_AVAILABLE ${SANITIZER_ADDR_FLAG})
if(SANITIZER_ADDR_AVAILABLE)
message(STATUS " Building with Address sanitizer")
append("${SANITIZER_ADDR_FLAG}" SANITIZER_SELECTED_FLAGS)
if(AFL)
append_quoteless(AFL_USE_ASAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(STATUS "Building with Memory sanitizer")
message(FATAL_ERROR "Address sanitizer not available for ${CMAKE_CXX_COMPILER}")
endif()
set(MEMCHECK_TYPE MemorySanitizer)
elseif(USE_SANITIZER MATCHES "([Uu]ndefined)")
message(STATUS "Building with Undefined sanitizer")
append("-fsanitize=undefined" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
endif()
if(USE_SANITIZER MATCHES "([Mm]emory([Ww]ith[Oo]rigins)?)")
# Optional: -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2
set(SANITIZER_MEM_FLAG "-fsanitize=memory")
if(USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)")
message(STATUS "Testing with MemoryWithOrigins sanitizer")
append("-fsanitize-memory-track-origins" SANITIZER_MEM_FLAG)
else()
message(STATUS "Testing with Memory sanitizer")
endif()
test_san_flags(SANITIZER_MEM_AVAILABLE ${SANITIZER_MEM_FLAG})
if(SANITIZER_MEM_AVAILABLE)
if(USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)")
message(STATUS " Building with MemoryWithOrigins sanitizer")
else()
message(STATUS " Building with Memory sanitizer")
endif()
append("${SANITIZER_MEM_FLAG}" SANITIZER_SELECTED_FLAGS)
if(AFL)
append_quoteless(AFL_USE_MSAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(FATAL_ERROR "Memory [With Origins] sanitizer not available for ${CMAKE_CXX_COMPILER}")
endif()
endif()
if(USE_SANITIZER MATCHES "([Uu]ndefined)")
message(STATUS "Testing with Undefined Behaviour sanitizer")
set(SANITIZER_UB_FLAG "-fsanitize=undefined")
if(EXISTS "${BLACKLIST_FILE}")
append("-fsanitize-blacklist=${BLACKLIST_FILE}" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
append("-fsanitize-blacklist=${BLACKLIST_FILE}" SANITIZER_UB_FLAG)
endif()
set(MEMCHECK_TYPE UndefinedBehaviorSanitizer)
elseif(USE_SANITIZER MATCHES "([Tt]hread)")
message(STATUS "Building with Thread sanitizer")
append("-fsanitize=thread" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
set(MEMCHECK_TYPE ThreadSanitizer)
elseif(USE_SANITIZER MATCHES "([Ll]eak)")
message(STATUS "Building with Leak sanitizer")
append("-fsanitize=leak" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
set(MEMCHECK_TYPE LeakSanitizer)
test_san_flags(SANITIZER_UB_AVAILABLE ${SANITIZER_UB_FLAG})
if(SANITIZER_UB_AVAILABLE)
message(STATUS " Building with Undefined Behaviour sanitizer")
append("${SANITIZER_UB_FLAG}" SANITIZER_SELECTED_FLAGS)
if(AFL)
append_quoteless(AFL_USE_UBSAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(FATAL_ERROR "Undefined Behaviour sanitizer not available for ${CMAKE_CXX_COMPILER}")
endif()
endif()
if(USE_SANITIZER MATCHES "([Tt]hread)")
message(STATUS "Testing with Thread sanitizer")
set(SANITIZER_THREAD_FLAG "-fsanitize=thread")
test_san_flags(SANITIZER_THREAD_AVAILABLE ${SANITIZER_THREAD_FLAG})
if(SANITIZER_THREAD_AVAILABLE)
message(STATUS " Building with Thread sanitizer")
append("${SANITIZER_THREAD_FLAG}" SANITIZER_SELECTED_FLAGS)
if(AFL)
append_quoteless(AFL_USE_TSAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(FATAL_ERROR "Thread sanitizer not available for ${CMAKE_CXX_COMPILER}")
endif()
endif()
if(USE_SANITIZER MATCHES "([Ll]eak)")
message(STATUS "Testing with Leak sanitizer")
set(SANITIZER_LEAK_FLAG "-fsanitize=leak")
test_san_flags(SANITIZER_LEAK_AVAILABLE ${SANITIZER_LEAK_FLAG})
if(SANITIZER_LEAK_AVAILABLE)
message(STATUS " Building with Leak sanitizer")
append("${SANITIZER_LEAK_FLAG}" SANITIZER_SELECTED_FLAGS)
if(AFL)
append_quoteless(AFL_USE_LSAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(FATAL_ERROR "Thread sanitizer not available for ${CMAKE_CXX_COMPILER}")
endif()
endif()
if(USE_SANITIZER MATCHES "([Cc][Ff][Ii])")
message(STATUS "Testing with Control Flow Integrity(CFI) sanitizer")
set(SANITIZER_CFI_FLAG "-fsanitize=cfi")
test_san_flags(SANITIZER_CFI_AVAILABLE ${SANITIZER_CFI_FLAG})
if(SANITIZER_CFI_AVAILABLE)
message(STATUS " Building with Control Flow Integrity(CFI) sanitizer")
append("${SANITIZER_LEAK_FLAG}" SANITIZER_SELECTED_FLAGS)
if(AFL)
append_quoteless(AFL_USE_CFISAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(FATAL_ERROR "Control Flow Integrity(CFI) sanitizer not available for ${CMAKE_CXX_COMPILER}")
endif()
endif()
message(STATUS "Sanitizer flags: ${SANITIZER_SELECTED_FLAGS}")
test_san_flags(SANITIZER_SELECTED_COMPATIBLE ${SANITIZER_SELECTED_FLAGS})
if(SANITIZER_SELECTED_COMPATIBLE)
message(STATUS " Building with ${SANITIZER_SELECTED_FLAGS}")
append("${SANITIZER_SELECTED_FLAGS}" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(
FATAL_ERROR "Unsupported value of USE_SANITIZER: ${USE_SANITIZER}")
message(FATAL_ERROR " Sanitizer flags ${SANITIZER_SELECTED_FLAGS} are not compatible.")
endif()
elseif(MSVC)
if(USE_SANITIZER MATCHES "([Aa]ddress)")
message(STATUS "Building with Address sanitizer")
append("-fsanitize=address" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
if(AFL)
append_quoteless(AFL_USE_ASAN=1 CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
endif()
else()
message(FATAL_ERROR "This sanitizer not yet supported in the MSVC environment: ${USE_SANITIZER}")
endif()
@ -93,7 +197,7 @@ if(USE_SANITIZER)
elseif(MSVC)
if(USE_SANITIZER MATCHES "([Aa]ddress)")
message(STATUS "Building with Address sanitizer")
append("/fsanitize=address" CMAKE_C_SANITIZER_FLAGS CMAKE_CXX_SANITIZER_FLAGS)
append("/fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(FATAL_ERROR "This sanitizer not yet supported in the MSVC environment: ${USE_SANITIZER}")
endif()

View File

@ -1,5 +1,5 @@
#
# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca
# Copyright (C) 2018-2023 by George Cave - gcave@stablecoder.ca
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
@ -13,91 +13,148 @@
# License for the specific language governing permissions and limitations under
# the License.
option(CLANG_TIDY "Turns on clang-tidy processing if it is found." OFF)
if(CLANG_TIDY)
find_program(CLANG_TIDY_EXE NAMES "clang-tidy")
mark_as_advanced(FORCE CLANG_TIDY_EXE)
if(CLANG_TIDY_EXE)
message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}")
set(CMAKE_C_CLANG_TIDY ${CLANG_TIDY_EXE} --checks=-*,clang-analyzer-*)#${ARGN})
set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} --checks=-*,clang-analyzer-*)#${ARGN})
else()
message(STATUS "clang-tidy not found!")
set(CMAKE_C_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it
set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it
# CLANG-TIDY
find_program(CLANG_TIDY_EXE NAMES "clang-tidy")
set(CLANG_TIDY_MESSAGE_OUTPUT # Control output messages to occur only once
FALSE
CACHE INTERNAL FALSE)
mark_as_advanced(FORCE CLANG_TIDY_EXE CMAKE_C_CLANG_TIDY CMAKE_CXX_CLANG_TIDY)
# Adds clang-tidy to code compiled after this macro. All arguments are added to
# the clang-tidy application call in the form of `clang-tidy ${ARGN}`.
#
# If the clang-tidy application is not found, the macro will cause CMake to
# produce an error and not generate.
#
# Options provided can be changed by calling the macro again with the new
# arguments.
macro(clang_tidy)
# Only want to output whether clang-tidy was found once
if(NOT CLANG_TIDY_MESSAGE_OUTPUT)
set(CLANG_TIDY_MESSAGE_OUTPUT TRUE)
if(CLANG_TIDY_EXE)
message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}")
else()
message(SEND_ERROR "clang-tidy not found!")
endif()
endif()
else()
#message(STATUS "clang-tidy not enabled!")
# Only pass the options if the tool was found
if(CLANG_TIDY_EXE)
set(CMAKE_C_CLANG_TIDY
${CLANG_TIDY_EXE} ${ARGN}
CACHE STRING "" FORCE)
set(CMAKE_CXX_CLANG_TIDY
${CLANG_TIDY_EXE} ${ARGN}
CACHE STRING "" FORCE)
endif()
endmacro()
# Clears clang-tidy so it is not called on any following defined code
# compilation. clang-tidy can be re-enabled by another call to `clang_tidy()`.
macro(reset_clang_tidy)
set(CMAKE_C_CLANG_TIDY
""
CACHE STRING "" FORCE) # delete it
CACHE STRING "" FORCE)
set(CMAKE_CXX_CLANG_TIDY
""
CACHE STRING "" FORCE) # delete it
endif()
# Adds clang-tidy checks to the compilation, with the given arguments being used
# as the options set.
macro(clang_tidy)
if(CLANG_TIDY AND CLANG_TIDY_EXE)
set(CMAKE_C_CLANG_TIDY ${CLANG_TIDY_EXE} ${ARGN})
set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} ${ARGN})
endif()
CACHE STRING "" FORCE)
endmacro()
option(IWYU "Turns on include-what-you-use processing if it is found." OFF)
if(IWYU)
find_program(IWYU_EXE NAMES "include-what-you-use")
mark_as_advanced(FORCE IWYU_EXE)
if(IWYU_EXE)
message(STATUS "include-what-you-use found: ${IWYU_EXE}")
else()
message(SEND_ERROR "Cannot enable include-what-you-use, as executable not found!")
set(CMAKE_C_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it
set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it
endif()
else()
#message(STATUS "include-what-you-use NOT ENABLED via 'IWYU' variable!")
set(CMAKE_C_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it
set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it
endif()
# INCLUDE-WHAT-YOU-USE
find_program(IWYU_EXE NAMES "include-what-you-use")
set(IWYU_MESSAGE_OUTPUT # Control output messages to occur only once
FALSE
CACHE INTERNAL FALSE)
mark_as_advanced(FORCE IWYU_EXE CMAKE_C_INCLUDE_WHAT_YOU_USE
CMAKE_CXX_INCLUDE_WHAT_YOU_USE)
# Adds include_what_you_use to the compilation, with the given arguments being
# used as the options set.
# Adds include-what-you-use to code compiled after this macro. All arguments are
# added to the include-what-you-use application call in the form of
# `include-what-you-use ${ARGN}`.
#
# If the include-what-you-use application is not found, the macro will cause
# CMake to produce an error and not generate.
#
# Options provided can be changed by calling the macro again with the new
# arguments.
macro(include_what_you_use)
if(IWYU AND IWYU_EXE)
set(CMAKE_C_INCLUDE_WHAT_YOU_USE ${IWYU_EXE} ${ARGN})
set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${IWYU_EXE} ${ARGN})
# Only want to output whether clang-tidy was found once
if(NOT IWYU_MESSAGE_OUTPUT)
set(IWYU_MESSAGE_OUTPUT TRUE)
if(IWYU_EXE)
message(STATUS "include-what-you-use found: ${IWYU_EXE}")
else()
message(SEND_ERROR "include-what-you-use not found!")
endif()
endif()
# Only pass the options if the tool was found
if(IWYU_EXE)
set(CMAKE_C_INCLUDE_WHAT_YOU_USE
${IWYU_EXE} ${ARGN}
CACHE STRING "" FORCE)
set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE
${IWYU_EXE} ${ARGN}
CACHE STRING "" FORCE)
endif()
endmacro()
option(CPPCHECK "Turns on cppcheck processing if it is found." OFF)
if(CPPCHECK)
if(CPPCHECK_EXE)
message(STATUS "cppcheck found: ${CPPCHECK_EXE}")
set(CMAKE_C_CPPCHECK
"${CPPCHECK_EXE};--enable=warning,performance,portability,missingInclude;--template=\"[{severity}][{id}] {message} {callstack} \(On {file}:{line}\)\";--suppress=missingIncludeSystem;--quiet;--verbose;--force"
)
set(CMAKE_CXX_CPPCHECK
"${CPPCHECK_EXE};--enable=warning,performance,portability,missingInclude;--template=\"[{severity}][{id}] {message} {callstack} \(On {file}:{line}\)\";--suppress=missingIncludeSystem;--quiet;--verbose;--force"
)
else()
message(SEND_ERROR "Cannot enable cppcheck, as executable not found!")
set(CMAKE_C_CPPCHECK "" CACHE STRING "" FORCE) # delete it
set(CMAKE_CXX_CPPCHECK "" CACHE STRING "" FORCE) # delete it
endif()
else()
# message(SEND_ERROR "cppcheck NOT ENABLED via 'CPPCHECK' variable!")
set(CMAKE_C_CPPCHECK "" CACHE STRING "" FORCE) # delete it
set(CMAKE_CXX_CPPCHECK "" CACHE STRING "" FORCE) # delete it
endif()
# Clears include-what-you-use so it is not called on any following defined code
# compilation. It can be re-enabled by another call to `include_what_you_use()`.
macro(reset_include_what_you_use)
set(CMAKE_C_INCLUDE_WHAT_YOU_USE
""
CACHE STRING "" FORCE)
set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE
""
CACHE STRING "" FORCE)
endmacro()
# Adds cppcheck to the compilation, with the given arguments being used as the
# options set.
# CPPCHECK
find_program(CPPCHECK_EXE NAMES "cppcheck")
set(CPPCHECK_MESSAGE_OUTPUT # Control output messages to occur only once
FALSE
CACHE INTERNAL FALSE)
mark_as_advanced(FORCE CPPCHECK_EXE CMAKE_C_CPPCHECK CMAKE_CXX_CPPCHECK)
# Adds cppcheck to code compiled after this macro. All arguments are added to
# the cppcheck application call in the form of `cppcheck ${ARGN}`.
#
# If the include-what-you-use application is not found, the macro will cause
# CMake to produce an error and not generate.
#
# Options provided can be changed by calling the macro again with the new
# arguments.
macro(cppcheck)
if(CPPCHECK AND CPPCHECK_EXE)
set(CMAKE_C_CPPCHECK ${CPPCHECK_EXE} ${ARGN})
set(CMAKE_CXX_CPPCHECK ${CPPCHECK_EXE} ${ARGN})
# Only want to output whether clang-tidy was found once
if(NOT CPPCHECK_MESSAGE_OUTPUT)
set(CPPCHECK_MESSAGE_OUTPUT TRUE)
if(CPPCHECK_EXE)
message(STATUS "cppcheck found: ${CPPCHECK_EXE}")
else()
message(SEND_ERROR "cppcheck not found!")
endif()
endif()
# Only pass the options if the tool was found
if(CPPCHECK_EXE)
set(CMAKE_C_CPPCHECK
${CPPCHECK_EXE} ${ARGN}
CACHE STRING "" FORCE)
set(CMAKE_CXX_CPPCHECK
${CPPCHECK_EXE} ${ARGN}
CACHE STRING "" FORCE)
endif()
endmacro()
# Clears include-what-you-use so it is not called on any following defined code
# compilation. It can be re-enabled by another call to `cppcheck()`.
macro(reset_cppcheck)
set(CMAKE_C_CPPCHECK
""
CACHE STRING "" FORCE)
set(CMAKE_CXX_CPPCHECK
""
CACHE STRING "" FORCE)
endmacro()