diff --git a/cmake/Jenkinsfile b/cmake/Jenkinsfile new file mode 100644 index 0000000..51df3c4 --- /dev/null +++ b/cmake/Jenkinsfile @@ -0,0 +1,25 @@ +pipeline { + agent none + + options { + // Only keep the 1 most recent builds + buildDiscarder(logRotator(numToKeepStr: "1")) + } + + +stages { + + stage("Build") + { + agent { + label 'c++' + } + + steps { + echo "build" + } + + } + + } +} diff --git a/cmake/Modules/CheckParent.cmake b/cmake/Modules/CheckParent.cmake new file mode 100644 index 0000000..c33bfd5 --- /dev/null +++ b/cmake/Modules/CheckParent.cmake @@ -0,0 +1 @@ +get_directory_property(hasParent PARENT_DIRECTORY) diff --git a/cmake/Modules/FindGMP.cmake b/cmake/Modules/FindGMP.cmake new file mode 100644 index 0000000..96d4a55 --- /dev/null +++ b/cmake/Modules/FindGMP.cmake @@ -0,0 +1,76 @@ +# Try to find the GMP library +# https://gmplib.org/ +# +# This module supports requiring a minimum version, e.g. you can do +# find_package(GMP 6.0.0) +# to require version 6.0.0 to newer of GMP. +# +# Once done this will define +# +# GMP_FOUND - system has GMP lib with correct version +# GMP_INCLUDES - the GMP include directory +# GMP_LIBRARIES - the GMP library +# GMP_VERSION - GMP version +# +# Copyright (c) 2016 Jack Poulson, +# Redistribution and use is allowed according to the terms of the BSD license. + +find_path(GMP_INCLUDES NAMES gmp.h PATHS $ENV{GMPDIR} ${INCLUDE_INSTALL_DIR}) + +# Set GMP_FIND_VERSION to 5.1.0 if no minimum version is specified +if(NOT GMP_FIND_VERSION) + if(NOT GMP_FIND_VERSION_MAJOR) + set(GMP_FIND_VERSION_MAJOR 5) + endif() + if(NOT GMP_FIND_VERSION_MINOR) + set(GMP_FIND_VERSION_MINOR 1) + endif() + if(NOT GMP_FIND_VERSION_PATCH) + set(GMP_FIND_VERSION_PATCH 0) + endif() + set(GMP_FIND_VERSION + "${GMP_FIND_VERSION_MAJOR}.${GMP_FIND_VERSION_MINOR}.${GMP_FIND_VERSION_PATCH}") +endif() + +message("GMP_INCLUDES=${GMP_INCLUDES}") +if(GMP_INCLUDES) + # Since the GMP version macros may be in a file included by gmp.h of the form + # gmp-.*[_]?.*.h (e.g., gmp-x86_64.h), we search each of them. + file(GLOB GMP_HEADERS "${GMP_INCLUDES}/gmp.h" "${GMP_INCLUDES}/gmp-*.h") + foreach(gmp_header_filename ${GMP_HEADERS}) + file(READ "${gmp_header_filename}" _gmp_version_header) + string(REGEX MATCH + "define[ \t]+__GNU_MP_VERSION[ \t]+([0-9]+)" _gmp_major_version_match + "${_gmp_version_header}") + if(_gmp_major_version_match) + set(GMP_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+__GNU_MP_VERSION_MINOR[ \t]+([0-9]+)" + _gmp_minor_version_match "${_gmp_version_header}") + set(GMP_MINOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+__GNU_MP_VERSION_PATCHLEVEL[ \t]+([0-9]+)" + _gmp_patchlevel_version_match "${_gmp_version_header}") + set(GMP_PATCHLEVEL_VERSION "${CMAKE_MATCH_1}") + set(GMP_VERSION + ${GMP_MAJOR_VERSION}.${GMP_MINOR_VERSION}.${GMP_PATCHLEVEL_VERSION}) + endif() + endforeach() + + # Check whether found version exists and exceeds the minimum requirement + if(NOT GMP_VERSION) + set(GMP_VERSION_OK FALSE) + message(STATUS "GMP version was not detected") + elseif(${GMP_VERSION} VERSION_LESS ${GMP_FIND_VERSION}) + set(GMP_VERSION_OK FALSE) + message(STATUS "GMP version ${GMP_VERSION} found in ${GMP_INCLUDES}, " + "but at least version ${GMP_FIND_VERSION} is required") + else() + set(GMP_VERSION_OK TRUE) + endif() +endif() + +find_library(GMP_LIBRARIES gmp PATHS $ENV{GMPDIR} ${LIB_INSTALL_DIR}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GMP DEFAULT_MSG + GMP_INCLUDES GMP_LIBRARIES GMP_VERSION_OK) +mark_as_advanced(GMP_INCLUDES GMP_LIBRARIES) diff --git a/cmake/Modules/FindValgrind.cmake b/cmake/Modules/FindValgrind.cmake new file mode 100644 index 0000000..bb1205d --- /dev/null +++ b/cmake/Modules/FindValgrind.cmake @@ -0,0 +1,25 @@ +# Find Valgrind. +# +# This module defines: +# VALGRIND_INCLUDE_DIR, where to find valgrind/memcheck.h, etc. +# VALGRIND_PROGRAM, the valgrind executable. +# VALGRIND_FOUND, If false, do not try to use valgrind. +# +# If you have valgrind installed in a non-standard place, you can define +# VALGRIND_PREFIX to tell cmake where it is. +# +# NOTE: Copied from the opencog project, where it is distributed under the +# terms of the New BSD License. + +message(STATUS "Valgrind Prefix: ${VALGRIND_PREFIX}") + +find_path(VALGRIND_INCLUDE_DIR valgrind/memcheck.h + /usr/include /usr/local/include ${VALGRIND_PREFIX}/include) +find_program(VALGRIND_PROGRAM NAMES valgrind PATH + /usr/bin /usr/local/bin ${VALGRIND_PREFIX}/bin) + +find_package_handle_standard_args(VALGRIND DEFAULT_MSG + VALGRIND_INCLUDE_DIR + VALGRIND_PROGRAM) + +mark_as_advanced(VALGRIND_INCLUDE_DIR VALGRIND_PROGRAM) diff --git a/cmake/Modules/GenerateCccc.cmake b/cmake/Modules/GenerateCccc.cmake new file mode 100644 index 0000000..9c6f1e3 --- /dev/null +++ b/cmake/Modules/GenerateCccc.cmake @@ -0,0 +1,47 @@ +INCLUDE(CheckParent) +# search for CCCC binary +FIND_PROGRAM(CCCC cccc ) + +# +# check if the GENERATE_CCCC function has already been defined +# +get_property(_GENERATE_CCCC GLOBAL PROPERTY _GENERATE_CCCC) +IF (NOT _GENERATE_CCCC) + +# set that we have defined GENERATE_CCCC +set_property(GLOBAL PROPERTY _GENERATE_CCCC "YES") + + + FUNCTION(GENERATE_CCCC) + IF(CCCC) + CMAKE_PARSE_ARGUMENTS(ARG "" "" "TARGETS" ${ARGN}) + get_property(_ccccfiles GLOBAL PROPERTY _ccccfiles) + foreach(_target ${ARG_TARGETS}) + get_target_property(_sources ${_target} SOURCES) + get_target_property(_source_dir ${_target} SOURCE_DIR) + + foreach(_source ${_sources}) + set(_fullsource "${_source_dir}/${_source}") + list(APPEND _ccccfiles "${_fullsource}") + endforeach() + endforeach() + set_property(GLOBAL PROPERTY _ccccfiles ${_ccccfiles}) + ENDIF() + ENDFUNCTION() + + FUNCTION(RESET_CCCC) + set_property(GLOBAL PROPERTY _ccccfiles "") + ENDFUNCTION() + + FUNCTION(GENERATE_CCCC_TARGET) + IF (NOT hasParent AND CCCC) + get_property(_targetccccfiles GLOBAL PROPERTY _ccccfiles) + + ADD_CUSTOM_TARGET(cccc + COMMAND ${CCCC} --outdir=cccc ${_targetccccfiles} + COMMENT "Generating cccc result") + ENDIF() + ENDFUNCTION() + + +ENDIF() diff --git a/cmake/Modules/GenerateCppCheck.cmake b/cmake/Modules/GenerateCppCheck.cmake new file mode 100644 index 0000000..a33ec73 --- /dev/null +++ b/cmake/Modules/GenerateCppCheck.cmake @@ -0,0 +1,74 @@ +INCLUDE(CheckParent) +find_program(CPPCHECK NAMES cppcheck) + +# +# check if the GENERATE_CPPCHECK function has already been defined +# +get_property(_GENERATE_CPPCHECK GLOBAL PROPERTY _GENERATE_CPPCHECK) +IF (NOT _GENERATE_CPPCHECK) + + # set that we have defined GENERATE_CCCC + set_property(GLOBAL PROPERTY _GENERATE_CPPCHECK "YES") + + FUNCTION(GENERATE_CPPCHECK) + IF(NOT TARGET cppcheck) + IF(CPPCHECK) + CMAKE_PARSE_ARGUMENTS(ARG "" "" "TARGETS" ${ARGN}) + get_property(_cppcheckfiles GLOBAL PROPERTY _cppcheckfiles) + get_property(_cppcheckincludedirs GLOBAL PROPERTY _cppcheckincludedirs) + + foreach(_target ${ARG_TARGETS}) + get_target_property(_sources ${_target} SOURCES) + get_target_property(_source_dir ${_target} SOURCE_DIR) + get_target_property(_include_dir ${_target} INCLUDE_DIRECTORIES) + string(REPLACE "$<" ";" _include_dirs ${_include_dir}) + + foreach(_dir ${_include_dirs}) + list(APPEND _cppcheckincludedirs -I${_include_dir}) + endforeach() + + foreach(_source ${_sources}) + set(_fullsource "${_source_dir}/${_source}") + list(APPEND _cppcheckfiles ${_fullsource}) + endforeach() + endforeach() + set_property(GLOBAL PROPERTY _cppcheckfiles ${_cppcheckfiles}) + set_property(GLOBAL PROPERTY _cppcheckincludedirs ${_cppcheckincludedirs}) + ENDIF() + ENDIF() + ENDFUNCTION() + + FUNCTION(RESET_CPPCHECK) + set_property(GLOBAL PROPERTY _cppcheckfiles "") + set_property(GLOBAL PROPERTY _cppcheckincludedirs "") + ENDFUNCTION() + + + FUNCTION(GENERATE_CPPCHECK_TARGET) + IF ( NOT hasParent AND CPPCHECK) + message("generate cppcheck target") + get_property(_targetcppcheckfiles GLOBAL PROPERTY _cppcheckfiles) + get_property(_targetcppcheckincludedirs GLOBAL PROPERTY _cppcheckincludedirs) + + add_custom_target(cppcheck + COMMAND + ${CPPCHECK} + --xml + --xml-version=2 + --enable=all + --inconclusive + --force + --inline-suppr + ${_targetcppcheckincludedirs} + ${_targetcppcheckfiles} + 2> cppcheck.xml + WORKING_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR} + COMMENT + "cppcheck: Running cppcheck on target ${_targetname}..." + VERBATIM) + + ENDIF() + ENDFUNCTION() + +ENDIF() diff --git a/cmake/Modules/ProcessDOXYGEN.cmake b/cmake/Modules/ProcessDOXYGEN.cmake new file mode 100644 index 0000000..a49dcb9 --- /dev/null +++ b/cmake/Modules/ProcessDOXYGEN.cmake @@ -0,0 +1,23 @@ +INCLUDE(CheckParent) + +IF(NOT hasParent) + find_package(Doxygen) + + + IF (DOXYGEN_FOUND) + # set input and output files + set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in) + set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + # request to configure the file + configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) + + # note the option ALL which allows to build the docs together with the application + add_custom_target( doxygen + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM ) + ENDIF() + +ENDIF() diff --git a/cmake/Modules/ProcessGIT.cmake b/cmake/Modules/ProcessGIT.cmake new file mode 100644 index 0000000..c2c6d13 --- /dev/null +++ b/cmake/Modules/ProcessGIT.cmake @@ -0,0 +1,17 @@ +if (GIT_FOUND) + execute_process( + COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +# Get the latest abbreviated commit hash of the working branch +execute_process( + COMMAND git log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +endif() diff --git a/cmake/Modules/add_my_test.cmake b/cmake/Modules/add_my_test.cmake new file mode 100644 index 0000000..d5a90f0 --- /dev/null +++ b/cmake/Modules/add_my_test.cmake @@ -0,0 +1,22 @@ +get_property(_ADD_MY_TEST GLOBAL PROPERTY _ADD_MY_TEST) +IF (NOT _ADD_MY_TEST) + + # set that we have defined GENERATE_CCCC + set_property(GLOBAL PROPERTY _ADD_MY_TEST "YES") + + + FUNCTION(ADD_MY_TEST) + CMAKE_PARSE_ARGUMENTS(ARG "" "TEST" "SOURCES;LIBS" ${ARGN}) + get_property(_mytests GLOBAL PROPERTY _mytests) + + list(APPEND _mytests "${ARG_TEST}") + + add_executable(${ARG_TEST} ${ARG_SOURCES}) + target_link_libraries(${ARG_TEST} ${ARG_LIBS}) + add_test(${ARG_TEST} ${ARG_TEST}) + + set_property(GLOBAL PROPERTY _mytests ${_mytests}) + ENDFUNCTION() + + +ENDIF() diff --git a/cmake/Modules/c++-standards.cmake b/cmake/Modules/c++-standards.cmake new file mode 100644 index 0000000..87a894f --- /dev/null +++ b/cmake/Modules/c++-standards.cmake @@ -0,0 +1,66 @@ +# +# Copyright (C) 2018 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. + +# Set the compiler standard to C++11 +macro(cxx_11) + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + + if(MSVC_VERSION GREATER_EQUAL "1900" AND CMAKE_VERSION LESS 3.10) + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("/std:c++11" _cpp_latest_flag_supported) + if(_cpp_latest_flag_supported) + add_compile_options("/std:c++11") + endif() + endif() +endmacro() + +# Set the compiler standard to C++14 +macro(cxx_14) + set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + + if(MSVC_VERSION GREATER_EQUAL "1900" AND CMAKE_VERSION LESS 3.10) + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("/std:c++14" _cpp_latest_flag_supported) + if(_cpp_latest_flag_supported) + add_compile_options("/std:c++14") + endif() + endif() +endmacro() + +# Set the compiler standard to C++17 +macro(cxx_17) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) + + if(MSVC_VERSION GREATER_EQUAL "1900" AND CMAKE_VERSION LESS 3.10) + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("/std:c++17" _cpp_latest_flag_supported) + if(_cpp_latest_flag_supported) + add_compile_options("/std:c++17") + endif() + endif() +endmacro() + +# Set the compiler standard to C++20 +macro(cxx_20) + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_EXTENSIONS OFF) +endmacro() diff --git a/cmake/Modules/code-coverage.cmake b/cmake/Modules/code-coverage.cmake new file mode 100644 index 0000000..6f1a917 --- /dev/null +++ b/cmake/Modules/code-coverage.cmake @@ -0,0 +1,588 @@ +# +# Copyright (C) 2018 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 any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target (instrumentation already added via global anyways) for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# 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. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH NAMES llvm-cov llvm-cov-8 llvm-cov-7 llvm-cov-6) +find_program(LLVM_PROFDATA_PATH NAMES llvm-profdata llvm-profdata-8 llvm-profdata-7 llvm-profdata-6) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) + +# 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) + + if("${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" + OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatability + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH + "[0-9]+\\.[0-9]+\\.[0-9]+" + LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + 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) + + # Used to get the shared object file list before doing the main all-processing + add_custom_target(ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_COMPILER_IS_GNUCXX) + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean + COMMAND ${LCOV_PATH} + --directory + ${CMAKE_BINARY_DIR} + --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# 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. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# 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-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. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# 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 - Excludes files of the patterns provided from coverage. **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ~~~ +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}) + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if("${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" + OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + target_compile_options( + ${TARGET_NAME} + PRIVATE -fprofile-instr-generate -fcoverage-mapping) + 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_COMPILER_IS_GNUCXX) + target_compile_options(${TARGET_NAME} + PRIVATE -fprofile-arcs -ftest-coverage) + target_link_libraries(${TARGET_NAME} PRIVATE gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # 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 "(Apple)?[Cc]lang" + OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${TARGET_NAME} + COMMAND echo + "-object=$" + >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs ccov-run-${TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if("${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 + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data + add_custom_target( + ccov-run-${TARGET_NAME} + COMMAND LLVM_PROFILE_FILE=${TARGET_NAME}.profraw + $ + COMMAND echo + "-object=$" + >> + ${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}) + + # 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}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target(ccov-show-${TARGET_NAME} + COMMAND ${LLVM_COV_PATH} + show + $ + ${SO_OBJECTS} + -instr-profile=${TARGET_NAME}.profdata + -show-line-counts-or-regions + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${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 + $ + ${SO_OBJECTS} + -instr-profile=${TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${TARGET_NAME} + COMMAND ${LLVM_COV_PATH} + show + $ + ${SO_OBJECTS} + -instr-profile=${TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME} + -format="html" + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${TARGET_NAME}) + + elseif(CMAKE_COMPILER_IS_GNUCXX) + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME}.info") + + # Run the executable, generating coverage information + add_custom_target(ccov-run-${TARGET_NAME} + COMMAND $ + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX + ${EXCLUDE_REGEX} + --remove + ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND + ${LCOV_PATH} + ${EXCLUDE_REGEX} + --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${TARGET_NAME} + COMMAND ${CMAKE_COMMAND} + -E + remove + ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} + --directory + ${CMAKE_BINARY_DIR} + --zerocounters + COMMAND $ + COMMAND ${LCOV_PATH} + --directory + ${CMAKE_BINARY_DIR} + --base-directory + ${CMAKE_SOURCE_DIR} + --capture + ${EXTERNAL_OPTION} + --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + COMMAND ${GENHTML_PATH} + -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${TARGET_NAME} POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${TARGET_NAME}) + + if(NOT CMAKE_COMPILER_IS_GNUCXX) + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies(ccov-report ccov-report-${TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing ccov-run-${TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if("${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_COMPILER_IS_GNUCXX) + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the regex patterns provided from coverage. +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets + "" + "" + "${multi_value_keywords}" + ${ARGN}) + + if(CODE_COVERAGE) + if("${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`) + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + 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) + + # 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) + + # 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) + + elseif(CMAKE_COMPILER_IS_GNUCXX) + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX + ${EXCLUDE_REGEX} + --remove + ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND + ${LCOV_PATH} + ${EXCLUDE_REGEX} + --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target(ccov-all + COMMAND ${LCOV_PATH} + --directory + ${CMAKE_BINARY_DIR} + --capture + --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + COMMAND ${GENHTML_PATH} + -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} + COMMAND ${CMAKE_COMMAND} + -E + remove + ${COVERAGE_INFO} + DEPENDS ccov-all-processing) + + endif() + + add_custom_command( + TARGET ccov-all POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/Modules/codecheck.cmake b/cmake/Modules/codecheck.cmake new file mode 100644 index 0000000..fadf623 --- /dev/null +++ b/cmake/Modules/codecheck.cmake @@ -0,0 +1,99 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. +# +option(CODECHECKER "Turns on codecheck processing if it is found." OFF) +option(CODECHECKER_STORE "Store results on central codechecker server" OFF) +option(CODECHECKER_RUN "Name of the codechecker run" "run-1") +option(CODECHECKER_URL "URL and product link to codechecker server" "http://localhost:8001/Default") + +find_program(CODECHECKER_VENV_PATH + NAME activate + PATHS ~/Apps/codechecker/venv/bin/ + ~/codechecker/venv/bin/ + /usr/src/codechecker/venv/bin/ + ) + +find_program(CODECHECKER_PATH + NAME CodeChecker + PATHS ~/Apps/codechecker/build/CodeChecker/bin/ + ~/codechecker/build/CodeChecker/bin/ + /usr/src/~/codechecker/build/CodeChecker/bin/ +) + + +execute_process( + COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE CODECHECK_GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + + + +if (CODECHECKER_PATH AND CODECHECKER_VENV_PATH) + message(STATUS "CodeChecker found") +else() + message(STATUS "CodeChecker not found") +endif() + +# export compile commands to json file to be used by atom c++ ide (clangd) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +set(CODECHECKER_COMPILE_COMMANDS "${CMAKE_BINARY_DIR}/compile_commands.json") + +# check if a skip file exists +if(EXISTS ${CMAKE_SOURCE_DIR}/codecheck.skip) + set(CODECHECKER_SKIP "-i${CMAKE_SOURCE_DIR}/codecheck.skip") +endif() + + +# output directory for codecheck analysis +set(CODECHECKER_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/codechecker_results) + +# html output directory +set(CODECHECKER_HTML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/codechecker_html) + +# Common initialization/checks +if(CODECHECKER AND CODECHECKER_PATH AND CODECHECKER_VENV_PATH AND NOT CODECHECKER_ADDED) + set(CODECHECKER_ADDED ON) + + add_custom_target(codechecker + COMMAND . ${CODECHECKER_VENV_PATH}\; + ${CODECHECKER_PATH} + analyze + ${CODECHECKER_SKIP} + -e sensitive + -j 4 + -o ${CODECHECKER_OUTPUT_DIRECTORY} + ${CODECHECKER_COMPILE_COMMANDS} + ) + + add_custom_target(codechecker-clean + COMMAND rm -rf ${CODECHECKER_OUTPUT_DIRECTORY} + ) + + + add_custom_target(codechecker-html + COMMAND . ${CODECHECKER_VENV_PATH}\; + ${CODECHECKER_PATH} + parse + ${CODECHECKER_OUTPUT_DIRECTORY} + -e html + -o ${CODECHECKER_HTML_OUTPUT_DIRECTORY} + ) + + if (CODECHECKER_STORE) + add_custom_target(codechecker-store + COMMAND . ${CODECHECKER_VENV_PATH}\; + ${CODECHECKER_PATH} + store + --tag ${CODECHECK_GIT_BRANCH} + -n ${CODECHECKER_RUN} + --url ${CODECHECKER_URL} + ${CODECHECKER_OUTPUT_DIRECTORY} + ) + endif() + + +endif() diff --git a/cmake/Modules/compiler-options.cmake b/cmake/Modules/compiler-options.cmake new file mode 100644 index 0000000..1d8a146 --- /dev/null +++ b/cmake/Modules/compiler-options.cmake @@ -0,0 +1,46 @@ +# +# Copyright (C) 2018 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. + +option(ENABLE_ALL_WARNINGS "Compile with all warnings for the major compilers." + OFF) +option(ENABLE_EFFECTIVE_CXX "Enable Effective C++ warnings." OFF) + +if(ENABLE_ALL_WARNINGS) + if(CMAKE_COMPILER_IS_GNUCXX) + # GCC + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") + elseif("${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" + OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + # Clang + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") + elseif(MSVC) + # MSVC + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + endif() +endif() + +if(ENABLE_EFFECTIVE_CXX) + if(CMAKE_COMPILER_IS_GNUCXX) + # GCC + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weffc++") + elseif("${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang" + OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + # Clang + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weffc++") + endif() +endif() diff --git a/cmake/Modules/defaultIncludes.cmake b/cmake/Modules/defaultIncludes.cmake new file mode 100644 index 0000000..b26abc4 --- /dev/null +++ b/cmake/Modules/defaultIncludes.cmake @@ -0,0 +1,13 @@ +include(c++-standards) +include(compiler-options) +include(sanitizers) +include(codecheck) +include(code-coverage) +include(tools) +include(GNUInstallDirs) +include(CTest) +include(doxygen) +include(ProcessGIT) +include(CheckParent) +include(add_my_test) +include(TestBigEndian) diff --git a/cmake/Modules/defaultOptions.cmake b/cmake/Modules/defaultOptions.cmake new file mode 100644 index 0000000..762037e --- /dev/null +++ b/cmake/Modules/defaultOptions.cmake @@ -0,0 +1,11 @@ +include(defaultIncludes) +find_package(Git) + + +enable_testing() +cxx_14() +build_docs(PROCESS_DOXYFILE DOXYFILE_PATH "docs/Doxyfile.in" ) +cppcheck("--enable=warning,performance,portability,missingInclude;--template=\"[{severity}][{id}] {message} {callstack} \(On {file}:{line}\)\";--suppress=missingIncludeSystem;--suppress=*:${PROJECT_SOURCE_DIR}/libs/*;--quiet;--verbose;--force") + +add_code_coverage_all_targets(EXCLUDE ${PROJECT_SOURCE_DIR}/libs/* ${PROJECT_SOURCE_DIR}/test/*) +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) diff --git a/cmake/Modules/doxygen.cmake b/cmake/Modules/doxygen.cmake new file mode 100644 index 0000000..754f35e --- /dev/null +++ b/cmake/Modules/doxygen.cmake @@ -0,0 +1,132 @@ +# +# Copyright (C) 2018 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_package(Doxygen) + +option(BUILD_DOCUMENTATION "Build API documentation using Doxygen. (make doc)" + ${DOXYGEN_FOUND}) + +# 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 +# The name to give the doc target. (Default: doc-${PROJECT_NAME}) +# +# OUTPUT_DIR +# The directory to place the generated output. (Default: ${CMAKE_CURRENT_BINARY_DIR}/doc) +# +# INSTALL_PATH +# The path to install the documenttation under. (if not specified, defaults to +# 'share/${PROJECT_NAME}) +# +# DOXYFILE_PATH +# The given doxygen file to use/process. (Defaults to'${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile') +# ~~~ +function(build_docs) + set(OPTIONS ADD_TO_DOC INSTALLABLE PROCESS_DOXYFILE) + set(SINGLE_VALUE_KEYWORDS + TARGET_NAME + INSTALL_PATH + DOXYFILE_PATH + OUTPUT_DIR) + set(MULTI_VALUE_KEYWORDS) + cmake_parse_arguments(build_docs + "${OPTIONS}" + "${SINGLE_VALUE_KEYWORDS}" + "${MULTI_VALUE_KEYWORDS}" + ${ARGN}) + + if(BUILD_DOCUMENTATION) + if(NOT DOXYGEN_FOUND) + message(FATAL_ERROR "Doxygen is needed to build the documentation.") + endif() + + if(NOT build_docs_DOXYFILE_PATH) + set(DOXYFILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile) + elseif(EXISTS ${build_docs_DOXYFILE_PATH}) + set(DOXYFILE_PATH ${build_docs_DOXYFILE_PATH}) + else() + set(DOXYFILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${build_docs_DOXYFILE_PATH}) + endif() + + if(NOT EXISTS ${DOXYFILE_PATH}) + message( + SEND_ERROR + "Could not find Doxyfile to use for procesing documentation at: ${DOXYFILE_PATH}" + ) + return() + endif() + + if(build_docs_PROCESS_DOXYFILE) + set(DOXYFILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + configure_file(${DOXYFILE_PATH} ${DOXYFILE} @ONLY) + else() + set(DOXYFILE ${DOXYFILE_PATH}) + endif() + + if(build_docs_OUTPUT_DIR) + set(OUT_DIR ${build_docs_OUTPUT_DIR}) + else() + set(OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doc) + endif() + + file(MAKE_DIRECTORY ${OUT_DIR}) + + if(build_docs_TARGET_NAME) + set(TARGET_NAME ${build_docs_TARGET_NAME}) + else() + set(TARGET_NAME doc-${PROJECT_NAME}) + endif() + + add_custom_target(${TARGET_NAME} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE} + WORKING_DIRECTORY ${OUT_DIR} + VERBATIM) + + if(build_docs_ADD_TO_DOC) + if(NOT TARGET doc) + add_custom_target(doc) + endif() + + add_dependencies(doc ${TARGET_NAME}) + endif() + + if(build_docs_INSTALLABLE) + if(NOT build_docs_INSTALL_PATH) + set(build_docs_INSTALL_PATH share/${PROJECT_NAME}) + endif() + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/ + COMPONENT documentation + DESTINATION ${build_docs_INSTALL_PATH}) + endif() + endif() +endfunction() diff --git a/cmake/Modules/sanitizers.cmake b/cmake/Modules/sanitizers.cmake new file mode 100644 index 0000000..f26947f --- /dev/null +++ b/cmake/Modules/sanitizers.cmake @@ -0,0 +1,87 @@ +# +# Copyright (C) 2018 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. + +set( + USE_SANITIZER + "" + CACHE + STRING + "Compile with a sanitizer. Options are: Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined'" + ) + +function(append value) + foreach(variable ${ARGN}) + set(${variable} "${${variable}} ${value}" PARENT_SCOPE) + endforeach(variable) +endfunction() + +if(USE_SANITIZER) + append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + + if(UNIX) + + if(uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG") + 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_FLAGS CMAKE_CXX_FLAGS) + elseif("${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_FLAGS CMAKE_CXX_FLAGS) + 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_FLAGS CMAKE_CXX_FLAGS) + if(USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)") + message(STATUS "Building with MemoryWithOrigins sanitizer") + append("-fsanitize-memory-track-origins" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + else() + message(STATUS "Building with Memory sanitizer") + endif() + elseif(USE_SANITIZER MATCHES "([Uu]ndefined)") + message(STATUS "Building with Undefined sanitizer") + append("-fsanitize=undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + if(EXISTS "${BLACKLIST_FILE}") + append("-fsanitize-blacklist=${BLACKLIST_FILE}" CMAKE_C_FLAGS + CMAKE_CXX_FLAGS) + endif() + elseif(USE_SANITIZER MATCHES "([Tt]hread)") + message(STATUS "Building with Thread sanitizer") + append("-fsanitize=thread" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + elseif(USE_SANITIZER MATCHES "([Ll]eak)") + message(STATUS "Building with Leak sanitizer") + append("-fsanitize=leak" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + else() + message( + FATAL_ERROR "Unsupported value of USE_SANITIZER: ${USE_SANITIZER}") + endif() + elseif(MSVC) + if(USE_SANITIZER MATCHES "([Aa]ddress)") + message(STATUS "Building with Address sanitizer") + 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() + else() + message(FATAL_ERROR "USE_SANITIZER is not supported on this platform.") + endif() + +endif() diff --git a/cmake/Modules/tools.cmake b/cmake/Modules/tools.cmake new file mode 100644 index 0000000..2fe9684 --- /dev/null +++ b/cmake/Modules/tools.cmake @@ -0,0 +1,94 @@ +# +# Copyright (C) 2018 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. + +option(CLANG_TIDY "Turns on clang-tidy processing if it is found." OFF) +option(IWYU "Turns on include-what-you-use processing if it is found." OFF) +option(CPPCHECK "Turns on cppcheck processing if it is found." OFF) + +# 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_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} ${ARGN}) + endif() +endmacro() + +# Adds include_what_you_use to the compilation, with the given arguments being +# used as the options set. +macro(include_what_you_use) + if(IWYU AND IWYU_EXE) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "${IWYU_EXE};${ARGN}") + endif() +endmacro() + +# Adds cppcheck to the compilation, with the given arguments being used as the +# options set. +macro(cppcheck) + if(CPPCHECK AND CPPCHECK_EXE) + set(CMAKE_CXX_CPPCHECK "${CPPCHECK_EXE};${ARGN}") + endif() +endmacro() + +find_program(CLANG_TIDY_EXE NAMES clang-tidy clang-tidy-8 clang-tidy-7 clang-tidy-6) +if(CLANG_TIDY_EXE) + message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") + if(NOT CLANG_TIDY) + message(STATUS "clang-tidy NOT ENABLED via 'CLANG_TIDY' variable!") + set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it + endif() +elseif(CLANG_TIDY) + message(SEND_ERROR "Cannot enable clang-tidy, as executable not found!") + set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it +else() + message(STATUS "clang-tidy not found!") + set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it +endif() + +find_program(IWYU_EXE NAMES "include-what-you-use") +if(IWYU_EXE) + message(STATUS "include-what-you-use found: ${IWYU_EXE}") + if(NOT IWYU) + message(STATUS "include-what-you-use NOT ENABLED via 'IWYU' variable!") + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it + endif() +elseif(IWYU) + message( + SEND_ERROR "Cannot enable include-what-you-use, as executable not found!") + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it +else() + message(STATUS "include-what-you-use not found!") + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "" CACHE STRING "" FORCE) # delete it +endif() + +find_program(CPPCHECK_EXE NAMES "cppcheck") +if(CPPCHECK_EXE) + message(STATUS "cppcheck found: ${CPPCHECK_EXE}") + if(CPPCHECK) + set( + CMAKE_CXX_CPPCHECK + "${CPPCHECK_EXE};--enable=warning,performance,portability,missingInclude;--template=\"[{severity}][{id}] {message} {callstack} \(On {file}:{line}\)\";--suppress=missingIncludeSystem;--quiet;--verbose;--force" + ) + endif() + if(NOT CPPCHECK) + message(STATUS "cppcheck NOT ENABLED via 'CPPCHECK' variable!") + set(CMAKE_CXX_CPPCHECK "" CACHE STRING "" FORCE) # delete it + endif() +elseif(CPPCHECK) + message(SEND_ERROR "Cannot enable cppcheck, as executable not found!") + set(CMAKE_CXX_CPPCHECK "" CACHE STRING "" FORCE) # delete it +else() + message(STATUS "cppcheck not found!") + set(CMAKE_CXX_CPPCHECK "" CACHE STRING "" FORCE) # delete it +endif() diff --git a/cmake/Toolchains/Toolchain-mingw64.cmake b/cmake/Toolchains/Toolchain-mingw64.cmake new file mode 100644 index 0000000..344b1bb --- /dev/null +++ b/cmake/Toolchains/Toolchain-mingw64.cmake @@ -0,0 +1,16 @@ +# the name of the target operating system +SET(CMAKE_SYSTEM_NAME Windows) + +# which compilers to use for C and C++ +SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) +SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) + +# here is the target environment located +SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, search +# programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)