From 8560fc63fdda5fa208e42e3389bec454eaa8870f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 14:42:38 -0400 Subject: [PATCH 01/22] initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..b43d50c1 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# subsys repo From 1b363f94a08f7a0a8f24085a131fe63ec728dbf1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 15:33:23 -0400 Subject: [PATCH 02/22] subsys: build+install (header-only library) --- CMakeLists.txt | 93 +++++ cmake/code-coverage.cmake | 678 +++++++++++++++++++++++++++++++++++ cmake/cxx.cmake | 96 +++++ cmake/subsysConfig.cmake.in | 4 + include/subsys/Subsystem.hpp | 303 ++++++++++++++++ 5 files changed, 1174 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 cmake/subsysConfig.cmake.in create mode 100644 include/subsys/Subsystem.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..c63d6740 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,93 @@ +cmake_minimum_required(VERSION 3.10) + +project(subsys VERSION 0.1) +enable_language(CXX) + +include(cmake/cxx.cmake) +include(cmake/code-coverage.cmake) + +enable_testing() +# activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) +add_code_coverage() + +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +set(XO_PROJECT_NAME subsys) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# always write compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + +# ---------------------------------------------------------------- +# - author's convenience: default install prefix to /home/$USER/local +# - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere + +if(NOT USER) + set(USER $ENV{USER}) +endif() + +# hmm. this works if explicitly given with cmake: +# cmake -DCMAKE_INSTALL_PREFIX=/home/roland/local path/to/source +# but not as default +if(NOT CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") +endif() +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") +endif() + +#add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# installing header-only library + +add_library(subsys INTERFACE) +target_include_directories(subsys INTERFACE + $ + $ +) +xo_install_library(subsys) + +# ---------------------------------------------------------------- +# cmake export +# (so this library works with cmake's find_package()) + +set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") + +include(CMakePackageConfigHelpers) +write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + VERSION 0.1 + COMPATIBILITY AnyNewerVersion +) + +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} + ) + +install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +install( + FILES + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + DESTINATION lib/cmake/${XO_PROJECT_NAME}) + +# ---------------------------------------------------------------- +# install .hpp + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + +# end CMakeLists.txt diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# 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 llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + 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' compatibility + 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 + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # 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-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} 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_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E 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_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(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 Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + 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_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + 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() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_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_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_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_code_coverage_COVERAGE_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(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_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + 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. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + 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 + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + 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 + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + 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/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 00000000..7fa32372 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,96 @@ +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated config.hpp file + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# variable +# XO_ADDRESS_SANITIZE +# determines whether to enable address sanitizer for the XO project +# (see toplevel CMakeLists.txt) +# ---------------------------------------------------------------- +if(XO_ADDRESS_SANITIZE) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF +set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + +# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON +# +# address sanitizer build complains about _FORTIFY_SOURCE redefines +# In file included from :460: +# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +# #define _FORTIFY_SOURCE 2 +# +set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + +# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +endif() + +# ---------------------------------------------------------------- +# generally want all the errors+warnings! +# however: address sanitizer generates error on _FORTIFY_SOURCE +# +macro(xo_compile_options target) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# +macro(xo_install_library target) + install( + TARGETS ${target} + EXPORT ${target}Targets + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) +endmacro() + +# ---------------------------------------------------------------- +# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +# +macro(xo_indentlogg_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/cmake/subsysConfig.cmake.in b/cmake/subsysConfig.cmake.in new file mode 100644 index 00000000..e13a2c54 --- /dev/null +++ b/cmake/subsysConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/subsys/Subsystem.hpp b/include/subsys/Subsystem.hpp new file mode 100644 index 00000000..b9a41e18 --- /dev/null +++ b/include/subsys/Subsystem.hpp @@ -0,0 +1,303 @@ +/* file Subsystem.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "indentlog/scope.hpp" +#include +#include +#include +#include + +/* e.g. XO_SUBSYSTEM_TAG(simulator) => xo::S_simulator_tag */ +#define XO_SUBSYSTEM_TAG(subsys_name) xo::S_ ## subsys_name ## _tag + +/* e.g. XO_SUBSYSTEM_REQUIRE(simulator) => + * xo::InitSubsys::require() + */ +#define XO_SUBSYSTEM_REQUIRE(subsys_name) xo::InitSubsys::require(); + +/* e.g. XO_SUBSYSTEM_PROVIDE(simulator, &init) => + * xo::Subsystem::provide("simulator", &init) + */ +#define XO_SUBSYSTEM_PROVIDE(subsys_name, init_addr) xo::Subsystem::provide(STRINGIFY(subsys_name), init_addr) + +//#define VERIFY_SUBSYSTEM(tag) Subsystem::verify_present(STRINGIFY(tag)) + +namespace xo { + using xo::tostr; + + /* evidence that one or more subsystems have been initialized. + * Used to prevent static linker stripping must-run initialization code + */ + class InitEvidence { + public: + InitEvidence() = default; + InitEvidence(std::uint64_t x) : evidence_{x} {} + + std::uint64_t evidence() const { return evidence_; } + + InitEvidence operator^=(InitEvidence x) { + this->evidence_ ^= x.evidence_; + + return *this; + } /*operator^=*/ + + InitEvidence operator^(InitEvidence x) { + return InitEvidence(this->evidence_ ^ x.evidence_); + } + + private: + /* we don't care about the specific value computed here, + * purpose is to be sufficiently impenentrable to compiler such + * that static linker can't optimize it away + */ + std::uint64_t evidence_ = 0; + }; /*InitEvidence*/ + + /* Goals: + * 1. provide for code that must run once (and only once) + * to initialize subsystems + * 2. in executable, want to run such code after main() starts + * instead of relying on static initializers; + * that way init behavior can be parameterized based on + * program arguments + * + * Use + * // subsystem foo + * + * enum Foo_tag {}; + * + * // guarantees that if anything gets initialized, then + * // foo_init() is included + * // + * template<> + * struct InitSubsys { + * static void foo_init() { ... } + * + * static InitEvidence require() { + * return Subsystem::require("foo", &foo_init); + * } + * }; + * + * .. register other subsystems .. + * + * Subsystem::initialize_all(); // foo_init() has been called once + * + * If subsystem bar depends on supporting subsystem {foo, quux}, then write: + * + * enum Bar_tag {}; + * + * template<> + * struct InitSubsys { + * static void bar_init() { ... } + * + * static InitEvidence require() { + * InitEvidence retval; + * + * retval ^= InitSubsys::require(); + * retval ^= InitSubsys::require(); + * + * retval ^= Subsystem::require("bar", &bar_init); + * } + * }; + * + * If using subsystems from a shared library (so no access to cmdline args etc): + * e.g. in pyfoo.cpp: + * + * InitEvidence s_pyfoo_init = InitSubsys::require(); + * or + * InitEvidence s_pyfoo_init = (InitSubsys::require() + * ^ InitSubsys::require()); + * + * Note: Tag argument here no relation of BuildTag in SubsystemImpl below + */ + template + struct InitSubsys {}; + + /* BuildTag: placeholder; insisting on header-only library */ + template + class SubsystemImpl { + public: + SubsystemImpl() = default; + SubsystemImpl(bool require_flag, + std::string_view subsys_name, + std::function init_fn) + : require_flag_{require_flag}, + subsys_name_{subsys_name}, + init_fn_{init_fn} {} + + /* establish an empty Subsystem record for subsys_name. + * record is _not_ linked into s_subsys_l! + * idempotent. + */ + template + static SubsystemImpl * establish() { + static SubsystemImpl s_subsys; + + return &s_subsys; + } /*establish*/ + + template + static bool verify_present(std::string subsys_tag) { + SubsystemImpl * subsys = establish(); + + if (!subsys->require_flag()) { + throw std::runtime_error(tostr("subsystem not present." + "(missing InitSubsys<", subsys_tag, ">::require() ?)")); + return false; + } + + return true; + } /*verify_present*/ + + /* provide (once only) initialization code for a subsystem with tag SubsystemTag. + * ideally this would be called just once for a particular tag; + * if called multiple times, calls after the first are no-ops. + */ + template + static InitEvidence provide(std::string_view subsys_name, + std::function init_fn) { + SubsystemImpl * subsys = establish(); + + provide_aux(subsys_name, init_fn, subsys); + + return InitEvidence(reinterpret_cast(subsys)); + } /*provide*/ + + /* throw exception if there's anything left for .initialize_all() to do, + * or subsystems have been added since last call to .initialize_all() + * Can use this to remind application author to call SubsystemImpl::initialize_all() + */ + static bool verify_all_initialized(); + + /* 1. initialize all subsystems: promise that for every preceding call + * to .require(), the corresponding initialization function has been + * run exactly once. + * 2. harmless to call this multiple times -- will not call any init_fn more than once + * 3. can interleave .initialize_all() with .require() as desired + */ + static InitEvidence initialize_all(); + + bool require_flag() const { return require_flag_; } + bool init_flag() const { return init_flag_; } + std::string_view subsys_name() const { return subsys_name_; } + + InitEvidence initialize(); + + private: + /* helper for .provide() */ + static void provide_aux(std::string_view subsys_name, + std::function init_fn, + SubsystemImpl * p_subsys); + + private: + /* set to true iff .s_subsys_l has been extended since last call to .initialize_all() */ + static bool s_dirty_flag; + /* one member for each unique call to .require() */ + static std::list s_subsys_l; + + private: + /* set to true on 1st call to .require() */ + bool require_flag_ = false; + /* set to true when .init_fn() invoked */ + bool init_flag_ = false; + /* unique subsystem name */ + std::string_view subsys_name_; + /* call this function once (at most) to initialize this subsystem */ + std::function init_fn_; + }; /*SubsystemImpl*/ + + template + bool + SubsystemImpl::s_dirty_flag = false; + + template + std::list *> + SubsystemImpl::s_subsys_l; + + template + void + SubsystemImpl::provide_aux(std::string_view subsys_name, + std::function init_fn, + SubsystemImpl * p_subsys) + { + if (!p_subsys->require_flag()) { + /* 1st call to .provide() for this SubsystemTag */ + + using xo::scope; + using xo::xtag; + + scope log(XO_ENTER0(chatty), + xtag("subsys", subsys_name), + xtag("address", p_subsys)); + + *p_subsys = SubsystemImpl(true /*require_flag*/, + subsys_name, + init_fn); + + s_dirty_flag = true; + s_subsys_l.push_back(p_subsys); + } + } /*provide_aux*/ + + template + bool + SubsystemImpl::verify_all_initialized() + { + if (s_dirty_flag) { + scope log(XO_ENTER0(error), "required subsystems NOT initialized!?"); + + for (SubsystemImpl * subsys : s_subsys_l) { + if (!subsys->init_flag()) { + log && log("missing InitSubsyssubsys_name(), "_tag>::require()"); + } + } + + throw std::runtime_error("Subsystem::verify_initialized:" + " Subsystem::initialize_all() never called, or out-of-date"); + return false; + } + + return true; + } /*verify_all_initialized*/ + + template + InitEvidence + SubsystemImpl::initialize_all() { + scope log(XO_ENTER0(chatty)); + + InitEvidence retval; + + if (s_dirty_flag) { + for (SubsystemImpl * subsys : s_subsys_l) { + log && log("init", xtag("subsys", subsys->subsys_name())); + + retval ^= subsys->initialize(); + } + } + + s_dirty_flag = false; + + return retval; + } /*initialize_all*/ + + template + InitEvidence + SubsystemImpl::initialize() + { + if (!init_flag_) { + init_flag_ = true; + + init_fn_(); + } + + return InitEvidence(reinterpret_cast(this)); + } /*initialize*/ + + using Subsystem = SubsystemImpl; +} /*namespace xo*/ + +/* end Subsystem.hpp */ From 539917e24c2c3985b89594947bc033b3df30848b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 15:34:08 -0400 Subject: [PATCH 03/22] subsys: README --- README.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b43d50c1..b7701781 100644 --- a/README.md +++ b/README.md @@ -1 +1,101 @@ -# subsys repo +# plugin initialization support + +subsys is a small header-only library providing support for plugin initialization + +## Features + +- provide application control of initialization order across c++ libraries +- circumvents the 'static order initialization fiasco' +- ensure initialization code runs exactly once if subsystem is linked +- enforce initialization order constraints +- defend against static linker stripping essential initialization code +- designed to work cleanly for libraries integrating into existing executable like python, java runtime, .. +- initialization state browseable at runtime + +## Getting Started + +### build + install `indentlog` dependency + +see [github/rconybea/indentlog](https://github.com/Rconybea/indentlog) + +### copy `subsys` repository locally +``` +$ git clone git@github.com:rconybea/subsys.git +$ ls -d subsys +subsys +``` + +### build + install +``` +$ cd subsys +$ mkdir build +$ cd build +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +`CMAKE_PREFIX_PATH` should point to prefix where `indentlog` is installed + +alternatively, if you're a nix user: +``` +$ git clone git@github.com:rconybea/xo-nix.git +$ ls -d xo-nix +xo-nix +$ cd xo-nix +$ nix-build -A subsys +``` + +### build for unit test coverage +``` +$ cd subsys +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## Examples + +### 1 +``` +// initialization code in .hpp for a subsystem foo, that relies on related subsystem bar + +#include "subsys/Subsystem.hpp" + +enum S_foo_tag {}; /* tag to represent initialization of subsystem foo */ + +template<> +struct InitSubsys { + static void init() { + // plugin initialization for subsystem foo + } + + static InitEvidence require() { + InitEvidence retval; + + // demand initialization of dependent subsystem bar, + // before initialization subsystem foo + // + retval ^= InitSubsys::require(); + + // initialization of this subsystem foo + retval ^= Subsystem::provide("foo", &init); + + return retval; + } +}; +``` + +``` +// in application code that relies on foo (perhaps along with other subsystems), +// for example in a pybind11 module: +// +PYBIND11_MODULE(pyfoo, m) { + // include foo in initialization set + InitSubsys::require(); + // ensure foo + dependencies are initialized + Subsystem::initialize_all(); + + ... +} +``` From 1c384e1ff0a01885bdf0713be858085814b6ece6 Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 24 Sep 2023 15:36:06 -0400 Subject: [PATCH 04/22] Create cmake-single-platform.yml --- .github/workflows/cmake-single-platform.yml | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/cmake-single-platform.yml diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml new file mode 100644 index 00000000..28c6f783 --- /dev/null +++ b/.github/workflows/cmake-single-platform.yml @@ -0,0 +1,39 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + From 5f025d8fc8c12ab8792faf71bc5baa93784efa5a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 09:23:55 -0400 Subject: [PATCH 05/22] build: adopt xo-cmake macros as dependency --- .github/workflows/cmake-single-platform.yml | 28 +++++++++++++++++---- CMakeLists.txt | 1 + README.md | 3 ++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 28c6f783..f654ea9c 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -22,18 +22,36 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Configure CMake + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Configure self (subsys) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build + - name: Build self (subsys) # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test + - name: Test self (subsys) working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} - diff --git a/CMakeLists.txt b/CMakeLists.txt index c63d6740..4418fe9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) enable_language(CXX) +include (xo_macros/xo_cxx) include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index b7701781..29b31f1a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ subsys $ cd subsys $ mkdir build $ cd build -$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install ``` From 155e1e5864969d49d0fb56a4138e33ced3055eb9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:20:40 -0400 Subject: [PATCH 06/22] subsys: build: use new xo-cmake macros --- CMakeLists.txt | 54 ++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4418fe9b..0c0b9c1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,37 +58,43 @@ target_include_directories(subsys INTERFACE $ $ ) -xo_install_library(subsys) +xo_install_library2(subsys) # ---------------------------------------------------------------- -# cmake export -# (so this library works with cmake's find_package()) +# provide find_package() support -set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -include(CMakePackageConfigHelpers) -write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - VERSION 0.1 - COMPATIBILITY AnyNewerVersion -) - -configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} - ) - -install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -install( - FILES - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - DESTINATION lib/cmake/${XO_PROJECT_NAME}) +## ---------------------------------------------------------------- +## cmake export +## (so this library works with cmake's find_package()) +# +#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +# +#include(CMakePackageConfigHelpers) +#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# VERSION 0.1 +# COMPATIBILITY AnyNewerVersion +#) +# +#configure_package_config_file( +# "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} +# ) +# +#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +#install( +# FILES +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# DESTINATION lib/cmake/${XO_PROJECT_NAME}) # ---------------------------------------------------------------- # install .hpp -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +xo_install_include_tree() +#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) # end CMakeLists.txt From bc4335af82b838378780140197a0c22e1e0b0531 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:21:17 -0400 Subject: [PATCH 07/22] subsys: build: streamline using xo-cmake macros --- CMakeLists.txt | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c0b9c1e..61140a28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(subsys VERSION 0.1) enable_language(CXX) include (xo_macros/xo_cxx) -include(cmake/cxx.cmake) +#include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) enable_testing() @@ -29,6 +29,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +xo_toplevel_compile_options() + # ---------------------------------------------------------------- # - author's convenience: default install prefix to /home/$USER/local # - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere @@ -54,10 +56,11 @@ endif() # installing header-only library add_library(subsys INTERFACE) -target_include_directories(subsys INTERFACE - $ - $ -) +xo_include_headeronly_options2(subsys) +#target_include_directories(subsys INTERFACE +# $ +# $ +#) xo_install_library2(subsys) # ---------------------------------------------------------------- @@ -65,36 +68,9 @@ xo_install_library2(subsys) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -## ---------------------------------------------------------------- -## cmake export -## (so this library works with cmake's find_package()) -# -#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") -# -#include(CMakePackageConfigHelpers) -#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# VERSION 0.1 -# COMPATIBILITY AnyNewerVersion -#) -# -#configure_package_config_file( -# "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} -# ) -# -#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -#install( -# FILES -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# DESTINATION lib/cmake/${XO_PROJECT_NAME}) - # ---------------------------------------------------------------- # install .hpp xo_install_include_tree() -#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) # end CMakeLists.txt From a8ec3021ebfadbd87a1427551d21e431bc358aa6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:33:43 -0400 Subject: [PATCH 08/22] subsys: build tidy --- CMakeLists.txt | 5 +- cmake/code-coverage.cmake | 678 -------------------------------------- cmake/cxx.cmake | 96 ------ 3 files changed, 2 insertions(+), 777 deletions(-) delete mode 100644 cmake/code-coverage.cmake delete mode 100644 cmake/cxx.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 61140a28..126d305b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,8 @@ cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) enable_language(CXX) -include (xo_macros/xo_cxx) -#include(cmake/cxx.cmake) -include(cmake/code-coverage.cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) enable_testing() # activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# 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 llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - 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' compatibility - 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 - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # 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-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} 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_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E 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_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(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 Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - 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_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - 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() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_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_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_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_code_coverage_COVERAGE_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(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_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - 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. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - 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 - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - 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 - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - 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/cxx.cmake b/cmake/cxx.cmake deleted file mode 100644 index 7fa32372..00000000 --- a/cmake/cxx.cmake +++ /dev/null @@ -1,96 +0,0 @@ -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code -# -macro(xo_include_options target) - # ---------------------------------------------------------------- - # PROJECT_SOURCE_DIR: - # so we can for example write - # #include "ordinaltree/foo.hpp" - # from anywhere in the project - # PROJECT_BINARY_DIR: - # since generated version file will be in build directory, - # need that build directory to also appear in - # compiler's include path - # - target_include_directories( - ${target} PUBLIC - $ # e.g. for #include "indentlog/scope.hpp" - $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src - $ - $ # e.g. for generated config.hpp file - ) - - # ---------------------------------------------------------------- - # make standard directories for std:: includes explicit - # so that - # (1) they appear in compile_commands.json. - # (2) clangd (run from emacs lsp-mode) can find them - # - if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() -endmacro() - -# ---------------------------------------------------------------- -# variable -# XO_ADDRESS_SANITIZE -# determines whether to enable address sanitizer for the XO project -# (see toplevel CMakeLists.txt) -# ---------------------------------------------------------------- -if(XO_ADDRESS_SANITIZE) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) -endif() - -# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF -set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) - -# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON -# -# address sanitizer build complains about _FORTIFY_SOURCE redefines -# In file included from :460: -# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] -# #define _FORTIFY_SOURCE 2 -# -set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) - -# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro -if(XO_ADDRESS_SANITIZE) - set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) -else() - set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -endif() - -# ---------------------------------------------------------------- -# generally want all the errors+warnings! -# however: address sanitizer generates error on _FORTIFY_SOURCE -# -macro(xo_compile_options target) - target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) -endmacro() - -# ---------------------------------------------------------------- -# use this for a subdir that builds a library -# -macro(xo_install_library target) - install( - TARGETS ${target} - EXPORT ${target}Targets - LIBRARY DESTINATION lib COMPONENT Runtime - ARCHIVE DESTINATION lib COMPONENT Development - RUNTIME DESTINATION bin COMPONENT Runtime - PUBLIC_HEADER DESTINATION include COMPONENT Development - BUNDLE DESTINATION bin COMPONENT Runtime - ) -endmacro() - -# ---------------------------------------------------------------- -# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers -# -macro(xo_indentlogg_dependency target) - find_package(indentlog REQUIRED) - #add_dependencies(${target} indentlog) - target_link_libraries(${target} PUBLIC indentlog) - #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) -endmacro() From 4012d7387f2e371e95e92546806d1b6be6630008 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:00:48 -0400 Subject: [PATCH 09/22] subsys: build: xo-macros default CMAKE_MODULE_PATH --- CMakeLists.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 126d305b..d308603a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,8 +28,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # - author's convenience: default install prefix to /home/$USER/local # - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere @@ -44,9 +42,8 @@ endif() if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") -endif() + +xo_toplevel_compile_options() #add_subdirectory(example) #add_subdirectory(utest) From 3ec624bd75d0801ffcb28fdf57f74a205cede9aa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:11:37 -0400 Subject: [PATCH 10/22] subsys: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d308603a..24b9e54a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,12 +19,6 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) set(XO_PROJECT_NAME subsys) -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) -endif() - -set(CMAKE_CXX_STANDARD_REQUIRED True) - # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") From 4600ebcb213914af4637b20eaad5cae5bd667dff Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:22:54 -0400 Subject: [PATCH 11/22] subsys: consolidate CMAKE_EXPORT_COMPILE_COMMANDS + tidy --- CMakeLists.txt | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24b9e54a..189db40a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,38 +19,13 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) set(XO_PROJECT_NAME subsys) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - -# ---------------------------------------------------------------- -# - author's convenience: default install prefix to /home/$USER/local -# - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere - -if(NOT USER) - set(USER $ENV{USER}) -endif() - -# hmm. this works if explicitly given with cmake: -# cmake -DCMAKE_INSTALL_PREFIX=/home/roland/local path/to/source -# but not as default -if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") -endif() - xo_toplevel_compile_options() -#add_subdirectory(example) -#add_subdirectory(utest) - # ---------------------------------------------------------------- # installing header-only library add_library(subsys INTERFACE) xo_include_headeronly_options2(subsys) -#target_include_directories(subsys INTERFACE -# $ -# $ -#) xo_install_library2(subsys) # ---------------------------------------------------------------- From 2b195c47f7a63f6ff5bfc3ead4eb4999ec9d7d50 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:09:21 -0400 Subject: [PATCH 12/22] subsys: insert xo/ into include path --- .gitignore | 2 ++ include/{ => xo}/subsys/Subsystem.hpp | 0 2 files changed, 2 insertions(+) create mode 100644 .gitignore rename include/{ => xo}/subsys/Subsystem.hpp (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d6bd1a25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# typical build location +build diff --git a/include/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp similarity index 100% rename from include/subsys/Subsystem.hpp rename to include/xo/subsys/Subsystem.hpp From ebfbd40afc752d160ae23c1b424094f14ec66d77 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 19:12:24 -0400 Subject: [PATCH 13/22] bugfix: include part must supply xo/ --- include/xo/subsys/Subsystem.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp index b9a41e18..ece72db6 100644 --- a/include/xo/subsys/Subsystem.hpp +++ b/include/xo/subsys/Subsystem.hpp @@ -5,7 +5,7 @@ #pragma once -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include #include #include From 50cf27b6a21b254f1fbdc92bf34537c0aa5d119b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:51:14 -0400 Subject: [PATCH 14/22] bugfix: xo/ in include path --- include/xo/subsys/Subsystem.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp index b9a41e18..ece72db6 100644 --- a/include/xo/subsys/Subsystem.hpp +++ b/include/xo/subsys/Subsystem.hpp @@ -5,7 +5,7 @@ #pragma once -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include #include #include From 6da0ceb35d88a326b567ab40c5206eeb5289602c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 16:57:05 -0400 Subject: [PATCH 15/22] subsys: + operator<< for InitEvidence --- include/xo/subsys/Subsystem.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/xo/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp index ece72db6..3cc6012a 100644 --- a/include/xo/subsys/Subsystem.hpp +++ b/include/xo/subsys/Subsystem.hpp @@ -6,6 +6,7 @@ #pragma once #include "xo/indentlog/scope.hpp" +#include #include #include #include @@ -57,6 +58,12 @@ namespace xo { std::uint64_t evidence_ = 0; }; /*InitEvidence*/ + inline std::ostream & + operator<<(std::ostream & os, InitEvidence x) { + os << ""; + return os; + } /*operator<<*/ + /* Goals: * 1. provide for code that must run once (and only once) * to initialize subsystems From 7ccc752b00d102cf31a0482294e192e82ed9d604 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:02:33 -0400 Subject: [PATCH 16/22] build: support symlink-only install --- CMakeLists.txt | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 189db40a..766058b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,25 +17,15 @@ add_code_coverage() # add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) -set(XO_PROJECT_NAME subsys) +#set(XO_PROJECT_NAME subsys) xo_toplevel_compile_options() # ---------------------------------------------------------------- # installing header-only library -add_library(subsys INTERFACE) -xo_include_headeronly_options2(subsys) -xo_install_library2(subsys) - -# ---------------------------------------------------------------- -# provide find_package() support - +xo_add_headeronly_library(subsys) +xo_install_library4(subsys ${PROJECT_NAME}Targets) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp - -xo_install_include_tree() - # end CMakeLists.txt From 6d5a14b33557e2ed9abf8a51682bf18ceb77288b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:20:33 -0400 Subject: [PATCH 17/22] bugfix: XO_PROJECT_NAME -> PROJECT_NAME in .cmake.in --- cmake/subsysConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/subsysConfig.cmake.in b/cmake/subsysConfig.cmake.in index e13a2c54..9c15f36a 100644 --- a/cmake/subsysConfig.cmake.in +++ b/cmake/subsysConfig.cmake.in @@ -1,4 +1,4 @@ @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From 8f11a8e413e22e2c651d8fda388875e2a5915ef3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:25:48 -0400 Subject: [PATCH 18/22] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 8 ++++++-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index d6bd1a25..9648517d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -# typical build location -build +# symlink to ${mybuilddirectory}/compile_commands.json for LSP +compile_commands.json +# LSP keeps state here +.cache +# typical build directories +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index 766058b1..9d6fef68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) enable_language(CXX) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) enable_testing() # activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 06bd4324305996db9b24a98c010a63c4593c1e21 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 19/22] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 5d02e74406e805962547c560fc68201809b003e2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 1 May 2024 14:42:00 -0400 Subject: [PATCH 20/22] xo-subsys: build: streamline using recent xo-cmake improvements --- CMakeLists.txt | 41 +++++++++++++++------------------ cmake/xo-bootstrap-macros.cmake | 35 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 189db40a..29f5bd4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,41 +1,38 @@ +# xo-subsys/CMakeLists.txt + cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) -enable_language(CXX) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) -enable_testing() -# activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) -add_code_coverage() +xo_cxx_toplevel_options2() + +# ---------------------------------------------------------------- +# cmake -DCMAKE_BUILD_TYPE=coverage +xo_toplevel_coverage_config2() + +# ---------------------------------------------------------------- +# cmake -DCMAKE_BUILD_TYPE=debug +xo_toplevel_debug_config2() -# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. -# we're not interested in code coverage for these sources. -# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; -# rather, want coverage on the code that the unit tests exercise. -# -add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) set(XO_PROJECT_NAME subsys) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # installing header-only library -add_library(subsys INTERFACE) -xo_include_headeronly_options2(subsys) -xo_install_library2(subsys) +set(SELF_LIB subsys) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +#add_library(subsys INTERFACE) +#xo_include_headeronly_options2(subsys) +#xo_install_library2(subsys) # ---------------------------------------------------------------- # provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From 32897d30172c5fe7a2ea3b3e2d3d6569de1eee4b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 1 May 2024 14:42:48 -0400 Subject: [PATCH 21/22] xo-subsys: .gitignore: ignore .build* dirs --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d6bd1a25..42168b59 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ # typical build location -build +.build* From 57eee82fa55d618694efd58b0e046575f314b25c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:31:04 -0400 Subject: [PATCH 22/22] .gitignore: + .projectile (emacs ws config) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9648517d..8e2ef56b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# emacs workspace config +.projectile # symlink to ${mybuilddirectory}/compile_commands.json for LSP compile_commands.json # LSP keeps state here