Skip to content

Commit

Permalink
Merge pull request #46 from lefticus/static_analysis_updates
Browse files Browse the repository at this point in the history
  • Loading branch information
aminya authored Feb 1, 2022
2 parents a1c1815 + bea3226 commit bf33860
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 69 deletions.
61 changes: 24 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,23 @@ Similar to `target_include_directories`, but it suppresses the warnings. It is u

## Changing the project_options parameters dynamically

It might be useful to change the test and development options on the fly (e.g., to enable sanitizers when running tests). To do this, you can include the `GlobalOptions.cmake`, which adds global options for the arguments of `project_options` function.
It might be useful to change the test and development options on the fly (e.g., to enable sanitizers when running tests). To do this, you can include the `DynamicOptions.cmake`, which adds `dynamic_project_options`. `dynamic_project_options` provides a recommended set of defaults (all static analysis and runtime analysis enabled for platforms where that is possible) while also providing a high level option `ENABLE_DEVELOPER_MODE` (defaulted to `ON`) which can be turned off for easy use by non-developers.

The goal of the `dynamic_project_options` is to give a safe and well analyzed environment to the developer by default, while simultaneously making it easy for a user of the project to compile while not having to worry about clang-tidy, sanitizers, cppcheck, etc.

The defaults presented to the user can be modified with

* `set(<featurename>_DEFAULT value)` - for user and developer builds
* `set(<featurename>_USER_DEFAULT value)` - for user builds
* `set(<featureoptionname>_DEVELOPER_DEFAULT value)` - for developer builds

If you need to fix a setting for the sake of a command-line configuration, you can use:

```
cmake -DOPT_<featurename>:BOOL=value
```


⚠️ It is highly recommended to keep the build declarative and reproducible by using the function arguments as explained above. The user of your code should not need to pass any special flags to build the library for normal usage. Please do not use this for anything other than test and development.

<details>
<summary>Click to show the example:</summary>
Expand All @@ -148,8 +162,8 @@ FetchContent_Declare(_project_options URL https://github.com/cpp-best-practices/
FetchContent_MakeAvailable(_project_options)
include(${_project_options_SOURCE_DIR}/Index.cmake)
# ❗ Add global CMake options
include(${_project_options_SOURCE_DIR}/src/GlobalOptions.cmake)
# ❗ Add dynamic CMake options
include(${_project_options_SOURCE_DIR}/src/DynamicOptions.cmake)
# uncomment to enable vcpkg:
# # Setup vcpkg - should be called before defining project()
Expand All @@ -158,43 +172,16 @@ include(${_project_options_SOURCE_DIR}/src/GlobalOptions.cmake)
# Set the project name and language
project(myproject LANGUAGES CXX)
# ❗ enable sanitizers if running the tests
option(FEATURE_TESTS "Enable the tests" OFF)
if(FEATURE_TESTS)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(ENABLE_SANITIZER_ADDRESS OFF)
set(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR OFF)
else()
set(ENABLE_SANITIZER_ADDRESS "ENABLE_SANITIZER_ADDRESS")
set(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "ENABLE_SANITIZER_UNDEFINED_BEHAVIOR")
endif()
endif()
# Set PCH to be on by default for all non-Developer Mode Builds
# (this is just intended as an example of what is possible)
set(ENABLE_PCH_USER_DEFAULT ON)
# Initialize project_options variable related to this project
# This overwrites `project_options` and sets `project_warnings`
# uncomment the options to enable them:
project_options(
ENABLE_CACHE
ENABLE_CPPCHECK
ENABLE_CLANG_TIDY
# WARNINGS_AS_ERRORS
# ENABLE_CONAN
# ENABLE_IPO
# ENABLE_INCLUDE_WHAT_YOU_USE
# ENABLE_COVERAGE
# ENABLE_PCH
# PCH_HEADERS
# ENABLE_DOXYGEN
# ENABLE_IPO
# ENABLE_USER_LINKER
# ENABLE_BUILD_WITH_TIME_TRACE
# ENABLE_UNITY
# ❗ Now, the address and undefined behavior sanitizers are enabled through CMake options
${ENABLE_SANITIZER_ADDRESS}
${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}
# ENABLE_SANITIZER_LEAK
# ENABLE_SANITIZER_THREAD
# ENABLE_SANITIZER_MEMORY
dynamic_project_options(
# set PCH headers you want enabled. Format can be slow, so this might be helpful
PCH_HEADERS <vector> <string> <fmt/format.h>
)
# add your executables, libraries, etc. here:
Expand Down
152 changes: 152 additions & 0 deletions src/DynamicProjectOptions.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# ENABLE_DEVELOPER_MODE: sets defaults appropriate for developers, this is defaulted to ON
# * WARNINGS_AS_ERRORS: ON
# * ENABLE_SANITIZER_ADDRESS: ON
# * ENABLE_CLANG_TIDY: ON for Ninja/Makefiles
# * ENABLE_SANITIZER_UNDEFINED: ON for Compilers that support it
# * ENABLE_CPPCHECK: ON for Ninja/Makefiles

# For non-developer builds
# -DENABLE_DEVELOPER_MODE:BOOL=OFF
# Is recommended

# In developer mode, all features have options that show up in the CMake GUI tools

# dynamic_project_options() macro enables all recommended defaults with appropriately
# applied options from the GUI which are set

# Any default can be overridden
# set(<feature_name>_DEFAULT <value>) - set default for both user and developer modes
# set(<feature_name>_DEVELOPER_DEFAULT <value>) - set default for developer mode
# set(<feature_name>_USER_DEFAULT <value>) - set default for user mode

option(ENABLE_DEVELOPER_MODE "Set up defaults for a developer of the project, and let developer change options" ON)

if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND NOT WIN32)
set(SUPPORTS_UBSAN ON)
else()
set(SUPPORTS_UBSAN OFF)
endif()

if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND WIN32)
set(SUPPORTS_ASAN OFF)
else()
set(SUPPORTS_ASAN ON)
endif()

# ccache, clang-tidy, cppcheck are only supported with Ninja and Makefile based generators
# note that it is possible to use Ninja with cl, so this still allows clang-tidy on Windows
# with CL.
#
# We are only setting the default options here. If the user attempts to enable
# these tools on a platform with unknown support, they are on their own.
#
# Also note, cppcheck has an option to be run on VCproj files, so we should investigate that
# Further note: MSVC2022 has builtin support for clang-tidy, but I can find
# no way to enable that via CMake
if(CMAKE_GENERATOR MATCHES ".*Makefile*." OR CMAKE_GENERATOR MATCHES ".*Ninja*")
set(MAKEFILE_OR_NINJA ON)
else()
set(MAKEFILE_OR_NINJA OFF)
endif()

include(CMakeDependentOption)

# <option name>;<user mode default>;<developer mode default>;<description>
set(options
"ENABLE_CACHE\;${MAKEFILE_OR_NINJA}\;${MAKEFILE_OR_NINJA}\;Enable ccache on Unix"
"WARNINGS_AS_ERRORS\;OFF\;ON\;Treat warnings as Errors"
"ENABLE_CLANG_TIDY\;OFF\;${MAKEFILE_OR_NINJA}\;Enable clang-tidy analysis during compilation"
"ENABLE_CONAN\;ON\;ON\;Automatically integrate Conan for package management"
"ENABLE_COVERAGE\;OFF\;OFF\;Analyze and report on coverage"
"ENABLE_SANITIZER_ADDRESS\;OFF\;${SUPPORTS_ASAN}\;Make memory errors into hard runtime errors (windows/linux/macos)"
"ENABLE_SANITIZER_UNDEFINED_BEHAVIOR\;OFF\;${SUPPORTS_UBSAN}\;Make certain types (numeric mostly) of undefined behavior into runtime errors"
"ENABLE_CPPCHECK\;OFF\;${MAKEFILE_OR_NINJA}\;Enable cppcheck analysis during compilation"
"ENABLE_IPO\;OFF\;OFF\;Enable whole-program optimization"
"ENABLE_INCLUDE_WHAT_YOU_USE\;OFF\;OFF\;Enable include-what-you-use analysis during compilation"
"ENABLE_PCH\;OFF\;OFF\;Enable pre-compiled-headers support"
"ENABLE_DOXYGEN\;OFF\;OFF\;Build documentation with Doxygen"
"ENABLE_USER_LINKER\;OFF\;OFF\;Allow custom linker settings"
"ENABLE_BUILD_WITH_TIME_TRACE\;OFF\;OFF\;Generates report of where compile-time is spent"
"ENABLE_UNITY\;OFF\;OFF\;Merge C++ files into larger C++ files, can speed up compilation sometimes"
"ENABLE_SANITIZER_LEAK\;OFF\;OFF\;Make memory leaks into hard runtime errors"
"ENABLE_SANITIZER_THREAD\;OFF\;OFF\;Make thread race conditions into hard runtime errors"
"ENABLE_SANITIZER_MEMORY\;OFF\;OFF\;Make other memory errors into runtime errors")

foreach(option ${options})
list(
GET
option
0
option_name)
list(
GET
option
1
option_user_default)
list(
GET
option
2
option_developer_default)
list(
GET
option
3
option_description)

if(DEFINED ${option_name}_DEFAULT)
if(DEFINED ${option_name}_DEVELOPER_DEFAULT OR DEFINED ${option_name}_USER_DEFAULT)
message(
SEND_ERROR
"You have separately defined user/developer defaults and general defaults for ${option_name}. Please either provide a general default OR separate developer/user overrides"
)
endif()

set(option_user_default ${${option_name}_DEFAULT})
set(option_developer_default ${${option_name}_DEFAULT})
endif()

if(DEFINED ${option_name}_USER_DEFAULT)
set(option_user_default ${${option_name}_USER_DEFAULT})
endif()

if(DEFINED ${option_name}_DEVELOPER_DEFAULT)
set(option_developer_default ${${option_name}_DEVELOPER_DEFAULT})
endif()

cmake_dependent_option(
OPT_${option_name}
"${option_description}"
${option_developer_default}
ENABLE_DEVELOPER_MODE
${option_user_default})

if(OPT_${option_name})
set(${option_name}_VALUE ${option_name})
else()
unset(${option_name}_VALUE)
endif()
endforeach()

macro(dynamic_project_options)
project_options(
${ENABLE_CONAN_VALUE}
${ENABLE_CACHE_VALUE}
${WARNINGS_AS_ERRORS_VALUE}
${ENABLE_CPPCHECK_VALUE}
${ENABLE_CLANG_TIDY_VALUE}
${ENABLE_COVERAGE_VALUE}
${ENABLE_IPO_VALUE}
${ENABLE_INCLUDE_WHAT_YOU_USE_VALUE}
${ENABLE_PCH_VALUE}
${ENABLE_DOXYGEN_VALUE}
${ENABLE_USER_LINKER_VALUE}
${ENABLE_BUILD_WITH_TIME_TRACE_VALUE}
${ENABLE_UNITY_VALUE}
${ENABLE_SANITIZER_ADDRESS_VALUE}
${ENABLE_SANITIZER_LEAK_VALUE}
${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR_VALUE}
${ENABLE_SANITIZER_THREAD_VALUE}
${ENABLE_SANITIZER_MEMORY_VALUE}
${ARGN})
endmacro()
27 changes: 0 additions & 27 deletions src/GlobalOptions.cmake

This file was deleted.

3 changes: 0 additions & 3 deletions src/Index.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,7 @@ macro(project_options)
enable_include_what_you_use()
endif()

# Very basic PCH example
if(${ProjectOptions_ENABLE_PCH})
# This sets a global PCH parameter, each project will build its own PCH, which is a good idea
# if any #define's change consider breaking this out per project as necessary
if(NOT ProjectOptions_PCH_HEADERS)
set(ProjectOptions_PCH_HEADERS
<vector>
Expand Down
9 changes: 7 additions & 2 deletions src/StaticAnalyzers.cmake
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
macro(enable_cppcheck)
find_program(CPPCHECK cppcheck)
if(CPPCHECK)
# Enable all warnings that are actionable by the user of this toolset
# style should enable the other 3, but we'll be explicit just in case
set(CMAKE_CXX_CPPCHECK
${CPPCHECK}
--suppress=missingInclude
--enable=all
--enable=style,performance,warning,portability
--inline-suppr
--inconclusive)
if(${CMAKE_CXX_STANDARD})
Expand All @@ -21,6 +22,10 @@ endmacro()
macro(enable_clang_tidy)
find_program(CLANGTIDY clang-tidy)
if(CLANGTIDY)
if(NOT CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" AND ${ProjectOptions_ENABLE_PCH})
message(SEND_ERROR "clang-tidy cannot be enabled with non-clang compiler and PCH, clang-tidy fails to handle gcc's PCH file")
endif()

set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option)
if(${CMAKE_CXX_STANDARD})
set(CMAKE_CXX_CLANG_TIDY ${CMAKE_CXX_CLANG_TIDY} -extra-arg=-std=c++${CMAKE_CXX_STANDARD})
Expand Down
28 changes: 28 additions & 0 deletions src/Utilities.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,31 @@ function(set_env_from_string env_string)
endif()
endforeach()
endfunction()


function(get_all_targets var)
set(targets)
get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR})
set(${var}
${targets}
PARENT_SCOPE)
endfunction()

macro(get_all_targets_recursive targets dir)
get_property(
subdirectories
DIRECTORY ${dir}
PROPERTY SUBDIRECTORIES)
foreach(subdir ${subdirectories})
get_all_targets_recursive(${targets} ${subdir})
endforeach()

get_property(
current_targets
DIRECTORY ${dir}
PROPERTY BUILDSYSTEM_TARGETS)
list(APPEND ${targets} ${current_targets})
endmacro()



0 comments on commit bf33860

Please sign in to comment.