summaryrefslogtreecommitdiffstats
path: root/cmake
diff options
context:
space:
mode:
Diffstat (limited to 'cmake')
-rw-r--r--cmake/CodeCoverage.cmake438
-rw-r--r--cmake/Hunter/config.cmake14
-rw-r--r--cmake/HunterGate.cmake528
-rw-r--r--cmake/coverage.cmake33
-rw-r--r--cmake/dev-mode.cmake32
-rw-r--r--cmake/docs-ci.cmake112
-rw-r--r--cmake/docs.cmake46
-rw-r--r--cmake/folders.cmake21
-rw-r--r--cmake/install-rules.cmake44
-rw-r--r--cmake/install-script.cmake18
-rw-r--r--cmake/lint-targets.cmake32
-rw-r--r--cmake/lint.cmake50
-rw-r--r--cmake/open-cpp-coverage.cmake.example31
-rw-r--r--cmake/prelude.cmake10
-rw-r--r--cmake/project-is-top-level.cmake6
-rw-r--r--cmake/spell-targets.cmake22
-rw-r--r--cmake/spell.cmake29
-rw-r--r--cmake/variables.cmake28
18 files changed, 1494 insertions, 0 deletions
diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake
new file mode 100644
index 0000000..2f54ee0
--- /dev/null
+++ b/cmake/CodeCoverage.cmake
@@ -0,0 +1,438 @@
+# Copyright (c) 2012 - 2017, Lars Bilke
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors
+# may be used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# CHANGES:
+#
+# 2012-01-31, Lars Bilke
+# - Enable Code Coverage
+#
+# 2013-09-17, Joakim Söderberg
+# - Added support for Clang.
+# - Some additional usage instructions.
+#
+# 2016-02-03, Lars Bilke
+# - Refactored functions to use named parameters
+#
+# 2017-06-02, Lars Bilke
+# - Merged with modified version from github.com/ufz/ogs
+#
+# 2019-05-06, Anatolii Kurotych
+# - Remove unnecessary --coverage flag
+#
+# 2019-12-13, FeRD (Frank Dana)
+# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor
+# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments.
+# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY
+# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list
+# - Set lcov basedir with -b argument
+# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be
+# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().)
+# - Delete output dir, .info file on 'make clean'
+# - Remove Python detection, since version mismatches will break gcovr
+# - Minor cleanup (lowercase function names, update examples...)
+#
+# 2019-12-19, FeRD (Frank Dana)
+# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets
+#
+# 2020-01-19, Bob Apthorpe
+# - Added gfortran support
+#
+# 2020-02-17, FeRD (Frank Dana)
+# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters
+# in EXCLUDEs, and remove manual escaping from gcovr targets
+#
+# USAGE:
+#
+# 1. Copy this file into your cmake modules path.
+#
+# 2. Add the following line to your CMakeLists.txt (best inside an if-condition
+# using a CMake option() to enable it just optionally):
+# include(CodeCoverage)
+#
+# 3. Append necessary compiler flags:
+# append_coverage_compiler_flags()
+#
+# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og
+#
+# 4. If you need to exclude additional directories from the report, specify them
+# using full paths in the COVERAGE_EXCLUDES variable before calling
+# setup_target_for_coverage_*().
+# Example:
+# set(COVERAGE_EXCLUDES
+# '${PROJECT_SOURCE_DIR}/src/dir1/*'
+# '/path/to/my/src/dir2/*')
+# Or, use the EXCLUDE argument to setup_target_for_coverage_*().
+# Example:
+# setup_target_for_coverage_lcov(
+# NAME coverage
+# EXECUTABLE testrunner
+# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*")
+#
+# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set
+# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR)
+# Example:
+# set(COVERAGE_EXCLUDES "dir1/*")
+# setup_target_for_coverage_gcovr_html(
+# NAME coverage
+# EXECUTABLE testrunner
+# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src"
+# EXCLUDE "dir2/*")
+#
+# 5. Use the functions described below to create a custom make target which
+# runs your test executable and produces a code coverage report.
+#
+# 6. Build a Debug build:
+# cmake -DCMAKE_BUILD_TYPE=Debug ..
+# make
+# make my_coverage_target
+#
+
+include(CMakeParseArguments)
+
+# Check prereqs
+find_program( GCOV_PATH gcov )
+find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl)
+find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat )
+find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
+find_program( CPPFILT_PATH NAMES c++filt )
+
+if(NOT GCOV_PATH)
+ message(FATAL_ERROR "gcov not found! Aborting...")
+endif() # NOT GCOV_PATH
+
+message("foo ${CMAKE_CXX_COMPILER_ID}")
+
+if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
+ if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3)
+ message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
+ endif()
+elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
+ if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang")
+ # Do nothing; exit conditional without error if true
+ elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU")
+ # Do nothing; exit conditional without error if true
+ else()
+ message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
+ endif()
+endif()
+
+set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage"
+ CACHE INTERNAL "")
+
+set(CMAKE_Fortran_FLAGS_COVERAGE
+ ${COVERAGE_COMPILER_FLAGS}
+ CACHE STRING "Flags used by the Fortran compiler during coverage builds."
+ FORCE )
+set(CMAKE_CXX_FLAGS_COVERAGE
+ ${COVERAGE_COMPILER_FLAGS}
+ CACHE STRING "Flags used by the C++ compiler during coverage builds."
+ FORCE )
+set(CMAKE_C_FLAGS_COVERAGE
+ ${COVERAGE_COMPILER_FLAGS}
+ CACHE STRING "Flags used by the C compiler during coverage builds."
+ FORCE )
+set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
+ ""
+ CACHE STRING "Flags used for linking binaries during coverage builds."
+ FORCE )
+set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
+ ""
+ CACHE STRING "Flags used by the shared libraries linker during coverage builds."
+ FORCE )
+mark_as_advanced(
+ CMAKE_Fortran_FLAGS_COVERAGE
+ CMAKE_CXX_FLAGS_COVERAGE
+ CMAKE_C_FLAGS_COVERAGE
+ CMAKE_EXE_LINKER_FLAGS_COVERAGE
+ CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
+
+if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
+ message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
+endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
+
+if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
+ link_libraries(gcov)
+endif()
+
+# Defines a target for running and collection code coverage information
+# Builds dependencies, runs the given executable and outputs reports.
+# NOTE! The executable should always have a ZERO as exit code otherwise
+# the coverage generation will not complete.
+#
+# setup_target_for_coverage_lcov(
+# NAME testrunner_coverage # New target name
+# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
+# DEPENDENCIES testrunner # Dependencies to build first
+# BASE_DIRECTORY "../" # Base directory for report
+# # (defaults to PROJECT_SOURCE_DIR)
+# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
+# # to BASE_DIRECTORY, with CMake 3.4+)
+# NO_DEMANGLE # Don't demangle C++ symbols
+# # even if c++filt is found
+# )
+function(setup_target_for_coverage_lcov)
+
+ set(options NO_DEMANGLE)
+ set(oneValueArgs BASE_DIRECTORY NAME)
+ set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS)
+ cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+ if(NOT LCOV_PATH)
+ message(FATAL_ERROR "lcov not found! Aborting...")
+ endif() # NOT LCOV_PATH
+
+ if(NOT GENHTML_PATH)
+ message(FATAL_ERROR "genhtml not found! Aborting...")
+ endif() # NOT GENHTML_PATH
+
+ # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
+ if(${Coverage_BASE_DIRECTORY})
+ get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
+ else()
+ set(BASEDIR ${PROJECT_SOURCE_DIR})
+ endif()
+
+ # Collect excludes (CMake 3.4+: Also compute absolute paths)
+ set(LCOV_EXCLUDES "")
+ foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES})
+ if(CMAKE_VERSION VERSION_GREATER 3.4)
+ get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
+ endif()
+ list(APPEND LCOV_EXCLUDES "${EXCLUDE}")
+ endforeach()
+ list(REMOVE_DUPLICATES LCOV_EXCLUDES)
+
+ # Conditional arguments
+ if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE})
+ set(GENHTML_EXTRA_ARGS "--demangle-cpp")
+ endif()
+
+ # Setup target
+ add_custom_target(${Coverage_NAME}
+
+ # Cleanup lcov
+ COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . -b ${BASEDIR} --zerocounters
+ # Create baseline to make sure untouched files show up in the report
+ COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b ${BASEDIR} -o ${Coverage_NAME}.base
+
+ # Run tests
+ COMMAND ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
+
+ # Capturing lcov counters and generating report
+ COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture
+ # add baseline counters
+ COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total
+ # filter collected data to final coverage report
+ COMMAND ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info
+
+ # Generate HTML output
+ COMMAND ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o ${Coverage_NAME} ${Coverage_NAME}.info
+
+ # Set output files as GENERATED (will be removed on 'make clean')
+ BYPRODUCTS
+ ${Coverage_NAME}.base
+ ${Coverage_NAME}.capture
+ ${Coverage_NAME}.total
+ ${Coverage_NAME}.info
+ ${Coverage_NAME} # report directory
+
+ WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+ DEPENDS ${Coverage_DEPENDENCIES}
+ VERBATIM # Protect arguments to commands
+ COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
+ )
+
+ # Show where to find the lcov info report
+ add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
+ COMMAND ;
+ COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info."
+ )
+
+ # Show info where to find the report
+ add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
+ COMMAND ;
+ COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
+ )
+
+endfunction() # setup_target_for_coverage_lcov
+
+# Defines a target for running and collection code coverage information
+# Builds dependencies, runs the given executable and outputs reports.
+# NOTE! The executable should always have a ZERO as exit code otherwise
+# the coverage generation will not complete.
+#
+# setup_target_for_coverage_gcovr_xml(
+# NAME ctest_coverage # New target name
+# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
+# DEPENDENCIES executable_target # Dependencies to build first
+# BASE_DIRECTORY "../" # Base directory for report
+# # (defaults to PROJECT_SOURCE_DIR)
+# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
+# # to BASE_DIRECTORY, with CMake 3.4+)
+# )
+function(setup_target_for_coverage_gcovr_xml)
+
+ set(options NONE)
+ set(oneValueArgs BASE_DIRECTORY NAME)
+ set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
+ cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+ if(NOT GCOVR_PATH)
+ message(FATAL_ERROR "gcovr not found! Aborting...")
+ endif() # NOT GCOVR_PATH
+
+ # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
+ if(${Coverage_BASE_DIRECTORY})
+ get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
+ else()
+ set(BASEDIR ${PROJECT_SOURCE_DIR})
+ endif()
+
+ # Collect excludes (CMake 3.4+: Also compute absolute paths)
+ set(GCOVR_EXCLUDES "")
+ foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
+ if(CMAKE_VERSION VERSION_GREATER 3.4)
+ get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
+ endif()
+ list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
+ endforeach()
+ list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
+
+ # Combine excludes to several -e arguments
+ set(GCOVR_EXCLUDE_ARGS "")
+ foreach(EXCLUDE ${GCOVR_EXCLUDES})
+ list(APPEND GCOVR_EXCLUDE_ARGS "-e")
+ list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
+ endforeach()
+
+ add_custom_target(${Coverage_NAME}
+ # Run tests
+ ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
+
+ # Running gcovr
+ COMMAND ${GCOVR_PATH} --xml
+ -r ${BASEDIR} ${GCOVR_EXCLUDE_ARGS}
+ --object-directory=${PROJECT_BINARY_DIR}
+ -o ${Coverage_NAME}.xml
+ BYPRODUCTS ${Coverage_NAME}.xml
+ WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+ DEPENDS ${Coverage_DEPENDENCIES}
+ VERBATIM # Protect arguments to commands
+ COMMENT "Running gcovr to produce Cobertura code coverage report."
+ )
+
+ # Show info where to find the report
+ add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
+ COMMAND ;
+ COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml."
+ )
+endfunction() # setup_target_for_coverage_gcovr_xml
+
+# Defines a target for running and collection code coverage information
+# Builds dependencies, runs the given executable and outputs reports.
+# NOTE! The executable should always have a ZERO as exit code otherwise
+# the coverage generation will not complete.
+#
+# setup_target_for_coverage_gcovr_html(
+# NAME ctest_coverage # New target name
+# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
+# DEPENDENCIES executable_target # Dependencies to build first
+# BASE_DIRECTORY "../" # Base directory for report
+# # (defaults to PROJECT_SOURCE_DIR)
+# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
+# # to BASE_DIRECTORY, with CMake 3.4+)
+# )
+function(setup_target_for_coverage_gcovr_html)
+
+ set(options NONE)
+ set(oneValueArgs BASE_DIRECTORY NAME)
+ set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
+ cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+ if(NOT GCOVR_PATH)
+ message(FATAL_ERROR "gcovr not found! Aborting...")
+ endif() # NOT GCOVR_PATH
+
+ # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR
+ if(${Coverage_BASE_DIRECTORY})
+ get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE)
+ else()
+ set(BASEDIR ${PROJECT_SOURCE_DIR})
+ endif()
+
+ # Collect excludes (CMake 3.4+: Also compute absolute paths)
+ set(GCOVR_EXCLUDES "")
+ foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES})
+ if(CMAKE_VERSION VERSION_GREATER 3.4)
+ get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR})
+ endif()
+ list(APPEND GCOVR_EXCLUDES "${EXCLUDE}")
+ endforeach()
+ list(REMOVE_DUPLICATES GCOVR_EXCLUDES)
+
+ # Combine excludes to several -e arguments
+ set(GCOVR_EXCLUDE_ARGS "")
+ foreach(EXCLUDE ${GCOVR_EXCLUDES})
+ list(APPEND GCOVR_EXCLUDE_ARGS "-e")
+ list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}")
+ endforeach()
+
+ add_custom_target(${Coverage_NAME}
+ # Run tests
+ ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}
+
+ # Create folder
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME}
+
+ # Running gcovr
+ COMMAND ${GCOVR_PATH} --html --html-details
+ -r ${BASEDIR} ${GCOVR_EXCLUDE_ARGS}
+ --object-directory=${PROJECT_BINARY_DIR}
+ -o ${Coverage_NAME}/index.html
+
+ BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME} # report directory
+ WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+ DEPENDS ${Coverage_DEPENDENCIES}
+ VERBATIM # Protect arguments to commands
+ COMMENT "Running gcovr to produce HTML code coverage report."
+ )
+
+ # Show info where to find the report
+ add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
+ COMMAND ;
+ COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report."
+ )
+
+endfunction() # setup_target_for_coverage_gcovr_html
+
+function(append_coverage_compiler_flags)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
+ set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
+ message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
+endfunction() # append_coverage_compiler_flags
diff --git a/cmake/Hunter/config.cmake b/cmake/Hunter/config.cmake
new file mode 100644
index 0000000..f731b2d
--- /dev/null
+++ b/cmake/Hunter/config.cmake
@@ -0,0 +1,14 @@
+
+hunter_config(
+ libpcre
+ VERSION 8.41
+ CMAKE_ARGS
+ EXTRA_FLAGS=--enable-unicode-properties --enable-jit --enable-utf
+)
+
+hunter_config(
+ readline
+ VERSION 6.3
+ CMAKE_ARGS
+ EXTRA_FLAGS=CFLAGS=-Wno-implicit-function-declaration
+)
diff --git a/cmake/HunterGate.cmake b/cmake/HunterGate.cmake
new file mode 100644
index 0000000..e78d3e8
--- /dev/null
+++ b/cmake/HunterGate.cmake
@@ -0,0 +1,528 @@
+# Copyright (c) 2013-2019, Ruslan Baratov
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# This is a gate file to Hunter package manager.
+# Include this file using `include` command and add package you need, example:
+#
+# cmake_minimum_required(VERSION 3.2)
+#
+# include("cmake/HunterGate.cmake")
+# HunterGate(
+# URL "https://github.com/path/to/hunter/archive.tar.gz"
+# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d"
+# )
+#
+# project(MyProject)
+#
+# hunter_add_package(Foo)
+# hunter_add_package(Boo COMPONENTS Bar Baz)
+#
+# Projects:
+# * https://github.com/hunter-packages/gate/
+# * https://github.com/ruslo/hunter
+
+option(HUNTER_ENABLED "Enable Hunter package manager support" ON)
+
+if(HUNTER_ENABLED)
+ if(CMAKE_VERSION VERSION_LESS "3.2")
+ message(
+ FATAL_ERROR
+ "At least CMake version 3.2 required for Hunter dependency management."
+ " Update CMake or set HUNTER_ENABLED to OFF."
+ )
+ endif()
+endif()
+
+include(CMakeParseArguments) # cmake_parse_arguments
+
+option(HUNTER_STATUS_PRINT "Print working status" ON)
+option(HUNTER_STATUS_DEBUG "Print a lot info" OFF)
+option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON)
+
+set(HUNTER_ERROR_PAGE "https://docs.hunter.sh/en/latest/reference/errors")
+
+function(hunter_gate_status_print)
+ if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG)
+ foreach(print_message ${ARGV})
+ message(STATUS "[hunter] ${print_message}")
+ endforeach()
+ endif()
+endfunction()
+
+function(hunter_gate_status_debug)
+ if(HUNTER_STATUS_DEBUG)
+ foreach(print_message ${ARGV})
+ string(TIMESTAMP timestamp)
+ message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}")
+ endforeach()
+ endif()
+endfunction()
+
+function(hunter_gate_error_page error_page)
+ message("------------------------------ ERROR ------------------------------")
+ message(" ${HUNTER_ERROR_PAGE}/${error_page}.html")
+ message("-------------------------------------------------------------------")
+ message("")
+ message(FATAL_ERROR "")
+endfunction()
+
+function(hunter_gate_internal_error)
+ message("")
+ foreach(print_message ${ARGV})
+ message("[hunter ** INTERNAL **] ${print_message}")
+ endforeach()
+ message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
+ message("")
+ hunter_gate_error_page("error.internal")
+endfunction()
+
+function(hunter_gate_fatal_error)
+ cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}")
+ if("${hunter_ERROR_PAGE}" STREQUAL "")
+ hunter_gate_internal_error("Expected ERROR_PAGE")
+ endif()
+ message("")
+ foreach(x ${hunter_UNPARSED_ARGUMENTS})
+ message("[hunter ** FATAL ERROR **] ${x}")
+ endforeach()
+ message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
+ message("")
+ hunter_gate_error_page("${hunter_ERROR_PAGE}")
+endfunction()
+
+function(hunter_gate_user_error)
+ hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data")
+endfunction()
+
+function(hunter_gate_self root version sha1 result)
+ string(COMPARE EQUAL "${root}" "" is_bad)
+ if(is_bad)
+ hunter_gate_internal_error("root is empty")
+ endif()
+
+ string(COMPARE EQUAL "${version}" "" is_bad)
+ if(is_bad)
+ hunter_gate_internal_error("version is empty")
+ endif()
+
+ string(COMPARE EQUAL "${sha1}" "" is_bad)
+ if(is_bad)
+ hunter_gate_internal_error("sha1 is empty")
+ endif()
+
+ string(SUBSTRING "${sha1}" 0 7 archive_id)
+
+ set(
+ hunter_self
+ "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
+ )
+
+ set("${result}" "${hunter_self}" PARENT_SCOPE)
+endfunction()
+
+# Set HUNTER_GATE_ROOT cmake variable to suitable value.
+function(hunter_gate_detect_root)
+ # Check CMake variable
+ string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty)
+ if(not_empty)
+ set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE)
+ hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable")
+ return()
+ endif()
+
+ # Check environment variable
+ string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty)
+ if(not_empty)
+ set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE)
+ hunter_gate_status_debug("HUNTER_ROOT detected by environment variable")
+ return()
+ endif()
+
+ # Check HOME environment variable
+ string(COMPARE NOTEQUAL "$ENV{HOME}" "" result)
+ if(result)
+ set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE)
+ hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable")
+ return()
+ endif()
+
+ # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only)
+ if(WIN32)
+ string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result)
+ if(result)
+ set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE)
+ hunter_gate_status_debug(
+ "HUNTER_ROOT set using SYSTEMDRIVE environment variable"
+ )
+ return()
+ endif()
+
+ string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result)
+ if(result)
+ set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE)
+ hunter_gate_status_debug(
+ "HUNTER_ROOT set using USERPROFILE environment variable"
+ )
+ return()
+ endif()
+ endif()
+
+ hunter_gate_fatal_error(
+ "Can't detect HUNTER_ROOT"
+ ERROR_PAGE "error.detect.hunter.root"
+ )
+endfunction()
+
+function(hunter_gate_download dir)
+ string(
+ COMPARE
+ NOTEQUAL
+ "$ENV{HUNTER_DISABLE_AUTOINSTALL}"
+ ""
+ disable_autoinstall
+ )
+ if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL)
+ hunter_gate_fatal_error(
+ "Hunter not found in '${dir}'"
+ "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'"
+ "Settings:"
+ " HUNTER_ROOT: ${HUNTER_GATE_ROOT}"
+ " HUNTER_SHA1: ${HUNTER_GATE_SHA1}"
+ ERROR_PAGE "error.run.install"
+ )
+ endif()
+ string(COMPARE EQUAL "${dir}" "" is_bad)
+ if(is_bad)
+ hunter_gate_internal_error("Empty 'dir' argument")
+ endif()
+
+ string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad)
+ if(is_bad)
+ hunter_gate_internal_error("HUNTER_GATE_SHA1 empty")
+ endif()
+
+ string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad)
+ if(is_bad)
+ hunter_gate_internal_error("HUNTER_GATE_URL empty")
+ endif()
+
+ set(done_location "${dir}/DONE")
+ set(sha1_location "${dir}/SHA1")
+
+ set(build_dir "${dir}/Build")
+ set(cmakelists "${dir}/CMakeLists.txt")
+
+ hunter_gate_status_debug("Locking directory: ${dir}")
+ file(LOCK "${dir}" DIRECTORY GUARD FUNCTION)
+ hunter_gate_status_debug("Lock done")
+
+ if(EXISTS "${done_location}")
+ # while waiting for lock other instance can do all the job
+ hunter_gate_status_debug("File '${done_location}' found, skip install")
+ return()
+ endif()
+
+ file(REMOVE_RECURSE "${build_dir}")
+ file(REMOVE_RECURSE "${cmakelists}")
+
+ file(MAKE_DIRECTORY "${build_dir}") # check directory permissions
+
+ # Disabling languages speeds up a little bit, reduces noise in the output
+ # and avoids path too long windows error
+ file(
+ WRITE
+ "${cmakelists}"
+ "cmake_minimum_required(VERSION 3.2)\n"
+ "project(HunterDownload LANGUAGES NONE)\n"
+ "include(ExternalProject)\n"
+ "ExternalProject_Add(\n"
+ " Hunter\n"
+ " URL\n"
+ " \"${HUNTER_GATE_URL}\"\n"
+ " URL_HASH\n"
+ " SHA1=${HUNTER_GATE_SHA1}\n"
+ " DOWNLOAD_DIR\n"
+ " \"${dir}\"\n"
+ " TLS_VERIFY\n"
+ " ${HUNTER_TLS_VERIFY}\n"
+ " SOURCE_DIR\n"
+ " \"${dir}/Unpacked\"\n"
+ " CONFIGURE_COMMAND\n"
+ " \"\"\n"
+ " BUILD_COMMAND\n"
+ " \"\"\n"
+ " INSTALL_COMMAND\n"
+ " \"\"\n"
+ ")\n"
+ )
+
+ if(HUNTER_STATUS_DEBUG)
+ set(logging_params "")
+ else()
+ set(logging_params OUTPUT_QUIET)
+ endif()
+
+ hunter_gate_status_debug("Run generate")
+
+ # Need to add toolchain file too.
+ # Otherwise on Visual Studio + MDD this will fail with error:
+ # "Could not find an appropriate version of the Windows 10 SDK installed on this machine"
+ if(EXISTS "${CMAKE_TOOLCHAIN_FILE}")
+ get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE)
+ set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}")
+ else()
+ # 'toolchain_arg' can't be empty
+ set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=")
+ endif()
+
+ string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make)
+ if(no_make)
+ set(make_arg "")
+ else()
+ # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM
+ set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}")
+ endif()
+
+ execute_process(
+ COMMAND
+ "${CMAKE_COMMAND}"
+ "-H${dir}"
+ "-B${build_dir}"
+ "-G${CMAKE_GENERATOR}"
+ "${toolchain_arg}"
+ ${make_arg}
+ WORKING_DIRECTORY "${dir}"
+ RESULT_VARIABLE download_result
+ ${logging_params}
+ )
+
+ if(NOT download_result EQUAL 0)
+ hunter_gate_internal_error(
+ "Configure project failed."
+ "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}"
+ "In directory ${dir}"
+ )
+ endif()
+
+ hunter_gate_status_print(
+ "Initializing Hunter workspace (${HUNTER_GATE_SHA1})"
+ " ${HUNTER_GATE_URL}"
+ " -> ${dir}"
+ )
+ execute_process(
+ COMMAND "${CMAKE_COMMAND}" --build "${build_dir}"
+ WORKING_DIRECTORY "${dir}"
+ RESULT_VARIABLE download_result
+ ${logging_params}
+ )
+
+ if(NOT download_result EQUAL 0)
+ hunter_gate_internal_error("Build project failed")
+ endif()
+
+ file(REMOVE_RECURSE "${build_dir}")
+ file(REMOVE_RECURSE "${cmakelists}")
+
+ file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}")
+ file(WRITE "${done_location}" "DONE")
+
+ hunter_gate_status_debug("Finished")
+endfunction()
+
+# Must be a macro so master file 'cmake/Hunter' can
+# apply all variables easily just by 'include' command
+# (otherwise PARENT_SCOPE magic needed)
+macro(HunterGate)
+ if(HUNTER_GATE_DONE)
+ # variable HUNTER_GATE_DONE set explicitly for external project
+ # (see `hunter_download`)
+ set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
+ endif()
+
+ # First HunterGate command will init Hunter, others will be ignored
+ get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET)
+
+ if(NOT HUNTER_ENABLED)
+ # Empty function to avoid error "unknown function"
+ function(hunter_add_package)
+ endfunction()
+
+ set(
+ _hunter_gate_disabled_mode_dir
+ "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode"
+ )
+ if(EXISTS "${_hunter_gate_disabled_mode_dir}")
+ hunter_gate_status_debug(
+ "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}"
+ )
+ list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}")
+ endif()
+ elseif(_hunter_gate_done)
+ hunter_gate_status_debug("Secondary HunterGate (use old settings)")
+ hunter_gate_self(
+ "${HUNTER_CACHED_ROOT}"
+ "${HUNTER_VERSION}"
+ "${HUNTER_SHA1}"
+ _hunter_self
+ )
+ include("${_hunter_self}/cmake/Hunter")
+ else()
+ set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}")
+
+ string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name)
+ if(_have_project_name)
+ hunter_gate_fatal_error(
+ "Please set HunterGate *before* 'project' command. "
+ "Detected project: ${PROJECT_NAME}"
+ ERROR_PAGE "error.huntergate.before.project"
+ )
+ endif()
+
+ cmake_parse_arguments(
+ HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV}
+ )
+
+ string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1)
+ string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url)
+ string(
+ COMPARE
+ NOTEQUAL
+ "${HUNTER_GATE_UNPARSED_ARGUMENTS}"
+ ""
+ _have_unparsed
+ )
+ string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global)
+ string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath)
+
+ if(_have_unparsed)
+ hunter_gate_user_error(
+ "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}"
+ )
+ endif()
+ if(_empty_sha1)
+ hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory")
+ endif()
+ if(_empty_url)
+ hunter_gate_user_error("URL suboption of HunterGate is mandatory")
+ endif()
+ if(_have_global)
+ if(HUNTER_GATE_LOCAL)
+ hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)")
+ endif()
+ if(_have_filepath)
+ hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)")
+ endif()
+ endif()
+ if(HUNTER_GATE_LOCAL)
+ if(_have_global)
+ hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)")
+ endif()
+ if(_have_filepath)
+ hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)")
+ endif()
+ endif()
+ if(_have_filepath)
+ if(_have_global)
+ hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)")
+ endif()
+ if(HUNTER_GATE_LOCAL)
+ hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)")
+ endif()
+ endif()
+
+ hunter_gate_detect_root() # set HUNTER_GATE_ROOT
+
+ # Beautify path, fix probable problems with windows path slashes
+ get_filename_component(
+ HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE
+ )
+ hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}")
+ if(NOT HUNTER_ALLOW_SPACES_IN_PATH)
+ string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces)
+ if(NOT _contain_spaces EQUAL -1)
+ hunter_gate_fatal_error(
+ "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces."
+ "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error"
+ "(Use at your own risk!)"
+ ERROR_PAGE "error.spaces.in.hunter.root"
+ )
+ endif()
+ endif()
+
+ string(
+ REGEX
+ MATCH
+ "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*"
+ HUNTER_GATE_VERSION
+ "${HUNTER_GATE_URL}"
+ )
+ string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty)
+ if(_is_empty)
+ set(HUNTER_GATE_VERSION "unknown")
+ endif()
+
+ hunter_gate_self(
+ "${HUNTER_GATE_ROOT}"
+ "${HUNTER_GATE_VERSION}"
+ "${HUNTER_GATE_SHA1}"
+ _hunter_self
+ )
+
+ set(_master_location "${_hunter_self}/cmake/Hunter")
+ get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
+ set(_done_location "${_archive_id_location}/DONE")
+ set(_sha1_location "${_archive_id_location}/SHA1")
+
+ # Check Hunter already downloaded by HunterGate
+ if(NOT EXISTS "${_done_location}")
+ hunter_gate_download("${_archive_id_location}")
+ endif()
+
+ if(NOT EXISTS "${_done_location}")
+ hunter_gate_internal_error("hunter_gate_download failed")
+ endif()
+
+ if(NOT EXISTS "${_sha1_location}")
+ hunter_gate_internal_error("${_sha1_location} not found")
+ endif()
+ file(READ "${_sha1_location}" _sha1_value)
+ string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal)
+ if(NOT _is_equal)
+ hunter_gate_internal_error(
+ "Short SHA1 collision:"
+ " ${_sha1_value} (from ${_sha1_location})"
+ " ${HUNTER_GATE_SHA1} (HunterGate)"
+ )
+ endif()
+ if(NOT EXISTS "${_master_location}")
+ hunter_gate_user_error(
+ "Master file not found:"
+ " ${_master_location}"
+ "try to update Hunter/HunterGate"
+ )
+ endif()
+ include("${_master_location}")
+ set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
+ endif()
+endmacro()
diff --git a/cmake/coverage.cmake b/cmake/coverage.cmake
new file mode 100644
index 0000000..c89cc16
--- /dev/null
+++ b/cmake/coverage.cmake
@@ -0,0 +1,33 @@
+# ---- Variables ----
+
+# We use variables separate from what CTest uses, because those have
+# customization issues
+set(
+ COVERAGE_TRACE_COMMAND
+ lcov -c -q
+ -o "${PROJECT_BINARY_DIR}/coverage.info"
+ -d "${PROJECT_BINARY_DIR}"
+ --include "${PROJECT_SOURCE_DIR}/*"
+ CACHE STRING
+ "; separated command to generate a trace for the 'coverage' target"
+)
+
+set(
+ COVERAGE_HTML_COMMAND
+ genhtml --legend -f -q
+ "${PROJECT_BINARY_DIR}/coverage.info"
+ -p "${PROJECT_SOURCE_DIR}"
+ -o "${PROJECT_BINARY_DIR}/coverage_html"
+ CACHE STRING
+ "; separated command to generate an HTML report for the 'coverage' target"
+)
+
+# ---- Coverage target ----
+
+add_custom_target(
+ coverage
+ COMMAND ${COVERAGE_TRACE_COMMAND}
+ COMMAND ${COVERAGE_HTML_COMMAND}
+ COMMENT "Generating coverage report"
+ VERBATIM
+)
diff --git a/cmake/dev-mode.cmake b/cmake/dev-mode.cmake
new file mode 100644
index 0000000..73a31fb
--- /dev/null
+++ b/cmake/dev-mode.cmake
@@ -0,0 +1,32 @@
+include(cmake/folders.cmake)
+
+include(CTest)
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
+add_custom_target(
+ run-exe
+ COMMAND lnav
+ VERBATIM
+)
+add_dependencies(run-exe lnav)
+
+option(BUILD_MCSS_DOCS "Build documentation using Doxygen and m.css" OFF)
+if(BUILD_MCSS_DOCS)
+ include(cmake/docs.cmake)
+endif()
+
+option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF)
+if(ENABLE_COVERAGE)
+ include(cmake/coverage.cmake)
+endif()
+
+if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
+ include(cmake/open-cpp-coverage.cmake OPTIONAL)
+endif()
+
+include(cmake/lint-targets.cmake)
+include(cmake/spell-targets.cmake)
+
+add_folders(Project)
diff --git a/cmake/docs-ci.cmake b/cmake/docs-ci.cmake
new file mode 100644
index 0000000..ae7f0c7
--- /dev/null
+++ b/cmake/docs-ci.cmake
@@ -0,0 +1,112 @@
+cmake_minimum_required(VERSION 3.14)
+
+foreach(var IN ITEMS PROJECT_BINARY_DIR PROJECT_SOURCE_DIR)
+ if(NOT DEFINED "${var}")
+ message(FATAL_ERROR "${var} must be defined")
+ endif()
+endforeach()
+set(bin "${PROJECT_BINARY_DIR}")
+set(src "${PROJECT_SOURCE_DIR}")
+
+# ---- Dependencies ----
+
+set(mcss_SOURCE_DIR "${bin}/docs/.ci")
+if(NOT IS_DIRECTORY "${mcss_SOURCE_DIR}")
+ file(MAKE_DIRECTORY "${mcss_SOURCE_DIR}")
+ file(
+ DOWNLOAD
+ https://github.com/friendlyanon/m.css/releases/download/release-1/mcss.zip
+ "${mcss_SOURCE_DIR}/mcss.zip"
+ STATUS status
+ EXPECTED_MD5 00cd2757ebafb9bcba7f5d399b3bec7f
+ )
+ if(NOT status MATCHES "^0;")
+ message(FATAL_ERROR "Download failed with ${status}")
+ endif()
+ execute_process(
+ COMMAND "${CMAKE_COMMAND}" -E tar xf mcss.zip
+ WORKING_DIRECTORY "${mcss_SOURCE_DIR}"
+ RESULT_VARIABLE result
+ )
+ if(NOT result EQUAL "0")
+ message(FATAL_ERROR "Extraction failed with ${result}")
+ endif()
+ file(REMOVE "${mcss_SOURCE_DIR}/mcss.zip")
+endif()
+
+find_program(Python3_EXECUTABLE NAMES python3 python)
+if(NOT Python3_EXECUTABLE)
+ message(FATAL_ERROR "Python executable was not found")
+endif()
+
+# ---- Process project() call in CMakeLists.txt ----
+
+file(READ "${src}/CMakeLists.txt" content)
+
+string(FIND "${content}" "project(" index)
+if(index EQUAL "-1")
+ message(FATAL_ERROR "Could not find \"project(\"")
+endif()
+string(SUBSTRING "${content}" "${index}" -1 content)
+
+string(FIND "${content}" "\n)\n" index)
+if(index EQUAL "-1")
+ message(FATAL_ERROR "Could not find \"\\n)\\n\"")
+endif()
+string(SUBSTRING "${content}" 0 "${index}" content)
+
+file(WRITE "${bin}/docs-ci.project.cmake" "docs_${content}\n)\n")
+
+macro(list_pop_front list out)
+ list(GET "${list}" 0 "${out}")
+ list(REMOVE_AT "${list}" 0)
+endmacro()
+
+function(docs_project name)
+ cmake_parse_arguments(PARSE_ARGV 1 "" "" "VERSION;DESCRIPTION;HOMEPAGE_URL" LANGUAGES)
+ set(PROJECT_NAME "${name}" PARENT_SCOPE)
+ if(DEFINED _VERSION)
+ set(PROJECT_VERSION "${_VERSION}" PARENT_SCOPE)
+ string(REGEX MATCH "^[0-9]+(\\.[0-9]+)*" versions "${_VERSION}")
+ string(REPLACE . ";" versions "${versions}")
+ set(suffixes MAJOR MINOR PATCH TWEAK)
+ while(NOT versions STREQUAL "" AND NOT suffixes STREQUAL "")
+ list_pop_front(versions version)
+ list_pop_front(suffixes suffix)
+ set("PROJECT_VERSION_${suffix}" "${version}" PARENT_SCOPE)
+ endwhile()
+ endif()
+ if(DEFINED _DESCRIPTION)
+ set(PROJECT_DESCRIPTION "${_DESCRIPTION}" PARENT_SCOPE)
+ endif()
+ if(DEFINED _HOMEPAGE_URL)
+ set(PROJECT_HOMEPAGE_URL "${_HOMEPAGE_URL}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+include("${bin}/docs-ci.project.cmake")
+
+# ---- Generate docs ----
+
+if(NOT DEFINED DOXYGEN_OUTPUT_DIRECTORY)
+ set(DOXYGEN_OUTPUT_DIRECTORY "${bin}/docs")
+endif()
+set(out "${DOXYGEN_OUTPUT_DIRECTORY}")
+
+foreach(file IN ITEMS Doxyfile conf.py)
+ configure_file("${src}/docs/${file}.in" "${bin}/docs/${file}" @ONLY)
+endforeach()
+
+set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py")
+set(config "${bin}/docs/conf.py")
+
+file(REMOVE_RECURSE "${out}/html" "${out}/xml")
+
+execute_process(
+ COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}"
+ WORKING_DIRECTORY "${bin}/docs"
+ RESULT_VARIABLE result
+)
+if(NOT result EQUAL "0")
+ message(FATAL_ERROR "m.css returned with ${result}")
+endif()
diff --git a/cmake/docs.cmake b/cmake/docs.cmake
new file mode 100644
index 0000000..286d027
--- /dev/null
+++ b/cmake/docs.cmake
@@ -0,0 +1,46 @@
+# ---- Dependencies ----
+
+set(extract_timestamps "")
+if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
+ set(extract_timestamps DOWNLOAD_EXTRACT_TIMESTAMP YES)
+endif ()
+
+include(FetchContent)
+FetchContent_Declare(
+ mcss URL
+ https://github.com/friendlyanon/m.css/releases/download/release-1/mcss.zip
+ URL_MD5 00cd2757ebafb9bcba7f5d399b3bec7f
+ SOURCE_DIR "${PROJECT_BINARY_DIR}/mcss"
+ UPDATE_DISCONNECTED YES
+ ${extract_timestamps}
+)
+FetchContent_MakeAvailable(mcss)
+
+find_package(Python3 3.6 REQUIRED)
+
+# ---- Declare documentation target ----
+
+set(
+ DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/docs"
+ CACHE PATH "Path for the generated Doxygen documentation"
+)
+
+set(working_dir "${PROJECT_BINARY_DIR}/docs")
+
+foreach (file IN ITEMS Doxyfile conf.py)
+ configure_file("docs/${file}.in" "${working_dir}/${file}" @ONLY)
+endforeach ()
+
+set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py")
+set(config "${working_dir}/conf.py")
+
+add_custom_target(
+ docs
+ COMMAND "${CMAKE_COMMAND}" -E remove_directory
+ "${DOXYGEN_OUTPUT_DIRECTORY}/html"
+ "${DOXYGEN_OUTPUT_DIRECTORY}/xml"
+ COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}"
+ COMMENT "Building documentation using Doxygen and m.css"
+ WORKING_DIRECTORY "${working_dir}"
+ VERBATIM
+)
diff --git a/cmake/folders.cmake b/cmake/folders.cmake
new file mode 100644
index 0000000..da7bd33
--- /dev/null
+++ b/cmake/folders.cmake
@@ -0,0 +1,21 @@
+set_property(GLOBAL PROPERTY USE_FOLDERS YES)
+
+# Call this function at the end of a directory scope to assign a folder to
+# targets created in that directory. Utility targets will be assigned to the
+# UtilityTargets folder, otherwise to the ${name}Targets folder. If a target
+# already has a folder assigned, then that target will be skipped.
+function(add_folders name)
+ get_property(targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
+ foreach(target IN LISTS targets)
+ get_property(folder TARGET "${target}" PROPERTY FOLDER)
+ if(DEFINED folder)
+ continue()
+ endif()
+ set(folder Utility)
+ get_property(type TARGET "${target}" PROPERTY TYPE)
+ if(NOT type STREQUAL "UTILITY")
+ set(folder "${name}")
+ endif()
+ set_property(TARGET "${target}" PROPERTY FOLDER "${folder}Targets")
+ endforeach()
+endfunction()
diff --git a/cmake/install-rules.cmake b/cmake/install-rules.cmake
new file mode 100644
index 0000000..ed43e76
--- /dev/null
+++ b/cmake/install-rules.cmake
@@ -0,0 +1,44 @@
+include(CMakePackageConfigHelpers)
+include(GNUInstallDirs)
+
+# find_package(<package>) call for consumers to find this project
+set(package lnav)
+
+install(
+ TARGETS lnav
+ RUNTIME COMPONENT lnav_Runtime
+)
+
+write_basic_package_version_file(
+ "${package}ConfigVersion.cmake"
+ COMPATIBILITY SameMajorVersion
+)
+
+# Allow package maintainers to freely override the path for the configs
+set(
+ lnav_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}"
+ CACHE PATH "CMake package config location relative to the install prefix"
+)
+mark_as_advanced(lnav_INSTALL_CMAKEDIR)
+
+install(
+ FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake"
+ DESTINATION "${lnav_INSTALL_CMAKEDIR}"
+ COMPONENT lnav_Development
+)
+
+# Export variables for the install script to use
+install(CODE "
+set(lnav_NAME [[$<TARGET_FILE_NAME:lnav>]])
+set(lnav_INSTALL_CMAKEDIR [[${lnav_INSTALL_CMAKEDIR}]])
+set(CMAKE_INSTALL_BINDIR [[${CMAKE_INSTALL_BINDIR}]])
+" COMPONENT lnav_Development)
+
+install(
+ SCRIPT cmake/install-script.cmake
+ COMPONENT lnav_Development
+)
+
+if(PROJECT_IS_TOP_LEVEL)
+ include(CPack)
+endif()
diff --git a/cmake/install-script.cmake b/cmake/install-script.cmake
new file mode 100644
index 0000000..6bd0c6e
--- /dev/null
+++ b/cmake/install-script.cmake
@@ -0,0 +1,18 @@
+file(
+ RELATIVE_PATH relative_path
+ "/${lnav_INSTALL_CMAKEDIR}"
+ "/${CMAKE_INSTALL_BINDIR}/${lnav_NAME}"
+)
+
+get_filename_component(prefix "${CMAKE_INSTALL_PREFIX}" ABSOLUTE)
+set(config_dir "${prefix}/${lnav_INSTALL_CMAKEDIR}")
+set(config_file "${config_dir}/lnavConfig.cmake")
+
+message(STATUS "Installing: ${config_file}")
+file(WRITE "${config_file}" "\
+set(
+ LNAV_EXECUTABLE
+ \"\${CMAKE_CURRENT_LIST_DIR}/${relative_path}\"
+ CACHE FILEPATH \"Path to the lnav executable\"
+)
+")
diff --git a/cmake/lint-targets.cmake b/cmake/lint-targets.cmake
new file mode 100644
index 0000000..1ffd8c0
--- /dev/null
+++ b/cmake/lint-targets.cmake
@@ -0,0 +1,32 @@
+set(
+ FORMAT_PATTERNS
+ src/*.cc src/*.hh
+ test/*.cc test/*.hh
+ CACHE STRING
+ "; separated patterns relative to the project source dir to format"
+)
+
+set(FORMAT_COMMAND clang-format CACHE STRING "Formatter to use")
+
+add_custom_target(
+ format-check
+ COMMAND "${CMAKE_COMMAND}"
+ -D "FORMAT_COMMAND=${FORMAT_COMMAND}"
+ -D "PATTERNS=${FORMAT_PATTERNS}"
+ -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
+ WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
+ COMMENT "Linting the code"
+ VERBATIM
+)
+
+add_custom_target(
+ format-fix
+ COMMAND "${CMAKE_COMMAND}"
+ -D "FORMAT_COMMAND=${FORMAT_COMMAND}"
+ -D "PATTERNS=${FORMAT_PATTERNS}"
+ -D FIX=YES
+ -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
+ WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
+ COMMENT "Fixing the code"
+ VERBATIM
+)
diff --git a/cmake/lint.cmake b/cmake/lint.cmake
new file mode 100644
index 0000000..32bfdc4
--- /dev/null
+++ b/cmake/lint.cmake
@@ -0,0 +1,50 @@
+cmake_minimum_required(VERSION 3.14)
+
+macro(default name)
+ if(NOT DEFINED "${name}")
+ set("${name}" "${ARGN}")
+ endif()
+endmacro()
+
+default(FORMAT_COMMAND clang-format)
+default(
+ PATTERNS
+ src/*.cc src/*.hh
+ test/*.cc test/*.hh
+)
+default(FIX NO)
+
+set(flag --output-replacements-xml)
+set(args OUTPUT_VARIABLE output)
+if(FIX)
+ set(flag -i)
+ set(args "")
+endif()
+
+file(GLOB_RECURSE files ${PATTERNS})
+set(badly_formatted "")
+set(output "")
+string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length)
+
+foreach(file IN LISTS files)
+ execute_process(
+ COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}"
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ RESULT_VARIABLE result
+ ${args}
+ )
+ if(NOT result EQUAL "0")
+ message(FATAL_ERROR "'${file}': formatter returned with ${result}")
+ endif()
+ if(NOT FIX AND output MATCHES "\n<replacement offset")
+ string(SUBSTRING "${file}" "${path_prefix_length}" -1 relative_file)
+ list(APPEND badly_formatted "${relative_file}")
+ endif()
+ set(output "")
+endforeach()
+
+if(NOT badly_formatted STREQUAL "")
+ list(JOIN badly_formatted "\n" bad_list)
+ message("The following files are badly formatted:\n\n${bad_list}\n")
+ message(FATAL_ERROR "Run again with FIX=YES to fix these files.")
+endif()
diff --git a/cmake/open-cpp-coverage.cmake.example b/cmake/open-cpp-coverage.cmake.example
new file mode 100644
index 0000000..b00f086
--- /dev/null
+++ b/cmake/open-cpp-coverage.cmake.example
@@ -0,0 +1,31 @@
+# Example file to run OpenCppCoverage on Windows
+
+include(ProcessorCount)
+ProcessorCount(N)
+
+file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/opencppcoverage")
+
+# Convert delimiters to Windows ones
+string(REPLACE "/" "\\" binary_dir "${PROJECT_BINARY_DIR}")
+string(REPLACE "/" "\\" source_dir "${PROJECT_SOURCE_DIR}")
+string(REPLACE "/" "\\" ctest "${CMAKE_CTEST_COMMAND}")
+
+add_custom_target(
+ win-cov
+ COMMAND OpenCppCoverage -q
+ # We want coverage from the child processes of CTest
+ --cover_children
+ # Subdirectory where the tests reside in the binary directory
+ --modules "${binary_dir}\\test"
+ # This command is for the developer, so export as html instead of cobertura
+ --export_type "html:${binary_dir}\\opencppcoverage"
+ # Source (not header) file locations
+ --sources "${source_dir}\\source"
+ --sources "${source_dir}\\test\\source"
+ # Working directory for CTest, which should be the binary directory
+ --working_dir "${binary_dir}"
+ # OpenCppCoverage should be run only with the Debug configuration tests
+ -- "${ctest}" -C Debug --output-on-failure -j "${N}"
+ WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
+ VERBATIM
+)
diff --git a/cmake/prelude.cmake b/cmake/prelude.cmake
new file mode 100644
index 0000000..c37d590
--- /dev/null
+++ b/cmake/prelude.cmake
@@ -0,0 +1,10 @@
+# ---- In-source guard ----
+
+if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
+ message(
+ FATAL_ERROR
+ "In-source builds are not supported. "
+ "Please read the BUILDING document before trying to build this project. "
+ "You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' first."
+ )
+endif()
diff --git a/cmake/project-is-top-level.cmake b/cmake/project-is-top-level.cmake
new file mode 100644
index 0000000..3435fc0
--- /dev/null
+++ b/cmake/project-is-top-level.cmake
@@ -0,0 +1,6 @@
+# This variable is set by project() in CMake 3.21+
+string(
+ COMPARE EQUAL
+ "${CMAKE_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}"
+ PROJECT_IS_TOP_LEVEL
+)
diff --git a/cmake/spell-targets.cmake b/cmake/spell-targets.cmake
new file mode 100644
index 0000000..0c21cab
--- /dev/null
+++ b/cmake/spell-targets.cmake
@@ -0,0 +1,22 @@
+set(SPELL_COMMAND codespell CACHE STRING "Spell checker to use")
+
+add_custom_target(
+ spell-check
+ COMMAND "${CMAKE_COMMAND}"
+ -D "SPELL_COMMAND=${SPELL_COMMAND}"
+ -P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
+ WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
+ COMMENT "Checking spelling"
+ VERBATIM
+)
+
+add_custom_target(
+ spell-fix
+ COMMAND "${CMAKE_COMMAND}"
+ -D "SPELL_COMMAND=${SPELL_COMMAND}"
+ -D FIX=YES
+ -P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
+ WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
+ COMMENT "Fixing spelling errors"
+ VERBATIM
+)
diff --git a/cmake/spell.cmake b/cmake/spell.cmake
new file mode 100644
index 0000000..e05ecd7
--- /dev/null
+++ b/cmake/spell.cmake
@@ -0,0 +1,29 @@
+cmake_minimum_required(VERSION 3.14)
+
+macro(default name)
+ if(NOT DEFINED "${name}")
+ set("${name}" "${ARGN}")
+ endif()
+endmacro()
+
+default(SPELL_COMMAND codespell)
+default(FIX NO)
+
+set(flag "")
+if(FIX)
+ set(flag -w)
+endif()
+
+execute_process(
+ COMMAND "${SPELL_COMMAND}" ${flag}
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ RESULT_VARIABLE result
+)
+
+if(result EQUAL "65")
+ message(FATAL_ERROR "Run again with FIX=YES to fix these errors.")
+elseif(result EQUAL "64")
+ message(FATAL_ERROR "Spell checker printed the usage info. Bad arguments?")
+elseif(NOT result EQUAL "0")
+ message(FATAL_ERROR "Spell checker returned with ${result}")
+endif()
diff --git a/cmake/variables.cmake b/cmake/variables.cmake
new file mode 100644
index 0000000..d4497e7
--- /dev/null
+++ b/cmake/variables.cmake
@@ -0,0 +1,28 @@
+# ---- Developer mode ----
+
+# Developer mode enables targets and code paths in the CMake scripts that are
+# only relevant for the developer(s) of lnav
+# Targets necessary to build the project must be provided unconditionally, so
+# consumers can trivially build and package the project
+if(PROJECT_IS_TOP_LEVEL)
+ option(lnav_DEVELOPER_MODE "Enable developer mode" OFF)
+endif()
+
+# ---- Warning guard ----
+
+# target_include_directories with the SYSTEM modifier will request the compiler
+# to omit warnings from the provided paths, if the compiler supports that
+# This is to provide a user experience similar to find_package when
+# add_subdirectory or FetchContent is used to consume this project
+set(warning_guard "")
+if(NOT PROJECT_IS_TOP_LEVEL)
+ option(
+ lnav_INCLUDES_WITH_SYSTEM
+ "Use SYSTEM modifier for lnav's includes, disabling warnings"
+ ON
+ )
+ mark_as_advanced(lnav_INCLUDES_WITH_SYSTEM)
+ if(lnav_INCLUDES_WITH_SYSTEM)
+ set(warning_guard SYSTEM)
+ endif()
+endif()