Files
plotter/cmake/plot_options.cmake
T

183 lines
8.2 KiB
CMake

# Shared build options and warning policy for the plot project.
#
# Included by the top-level CMakeLists.txt; every subdirectory target is
# expected to run plot_apply_warnings(<target>) so the core stays warning-clean
# under -Wall -Wextra -Wpedantic -Wconversion -Wshadow.
if(CMAKE_VERSION VERSION_LESS 3.24)
message(FATAL_ERROR "plot requires CMake 3.24 or newer (found ${CMAKE_VERSION})")
endif()
# Adapters and examples pull in third-party SDKs, so they default to OFF; the
# core library and its tests build on a bare toolchain.
option(PLOT_BUILD_TESTS "Build the plot test suite" ON)
option(PLOT_BUILD_EXAMPLES "Build the example programs" OFF)
option(PLOT_BUILD_GLFW "Build the GLFW adapter and demo" OFF)
option(PLOT_BUILD_IMGUI "Build the Dear ImGui adapter and demo" OFF)
option(PLOT_BUILD_QT6 "Build the Qt6 adapter and demo" OFF)
option(PLOT_BUILD_GTK4 "Build the GTK4 adapter and demo" OFF)
option(PLOT_INSTALL "Generate install and export rules (find_package(plot))" ON)
set(PLOT_SANITIZE "" CACHE STRING
"Sanitizers to build with, as the list a -fsanitize= flag takes (e.g. address); empty for none")
# The reproducible coverage report lives in tools/coverage.sh, and its `coverage`
# CMake target is offered only in a tree that is actually instrumented -- the
# target re-runs the capture, and a report over an uninstrumented build is a
# lie with numbers in it. The linux-coverage preset sets this; the other three
# presets leave it off, so their target lists, configure time and build product
# are exactly what they were before this option existed.
option(PLOT_COVERAGE "Add the `coverage` target (needs a --coverage build tree)" OFF)
if(PLOT_COVERAGE AND NOT CMAKE_CXX_FLAGS MATCHES "--coverage")
message(FATAL_ERROR
"plot: PLOT_COVERAGE=ON needs a --coverage build (CMAKE_CXX_FLAGS is \"${CMAKE_CXX_FLAGS}\"). "
"Configure with --preset linux-coverage, or drop -DPLOT_COVERAGE=ON.")
endif()
if(PLOT_COVERAGE AND NOT PLOT_BUILD_TESTS)
message(FATAL_ERROR
"plot: PLOT_COVERAGE=ON needs PLOT_BUILD_TESTS=ON -- the coverage report measures what "
"the test suite executes, so a coverage tree with no suite has nothing to report.")
endif()
# Sanitizer flags go on every target in this project, fetched third-party sources
# included: a partially instrumented build reports as loudly about the
# instrumentation gap as about any real bug. The flags are one -fsanitize= list
# (PLOT_SANITIZE) rather than a set of per-sanitizer options so the preset, the
# tests' runtime environment and this file cannot disagree about what is on.
if(PLOT_SANITIZE)
add_compile_options(-fsanitize=${PLOT_SANITIZE} -fno-omit-frame-pointer)
add_link_options(-fsanitize=${PLOT_SANITIZE})
endif()
# Read by tests/CMakeLists.txt, which points ASAN_OPTIONS at this project's
# suppressions file when the address sanitizer is on.
string(REPLACE "," ";" PLOT_SANITIZE_LIST "${PLOT_SANITIZE}")
if("address" IN_LIST PLOT_SANITIZE_LIST)
set(PLOT_ASAN_ENABLED TRUE)
endif()
# --- install / export bookkeeping --------------------------------------------
#
# An adapter can only be installed and exported when its toolkit can be found
# again on the consumer's machine. A dependency resolved by find_package() or
# pkg-config can be: the installed config file re-finds it with the very same
# call. A dependency that FetchContent supplied cannot -- there is nothing on the
# consumer's machine to point at, and promising it would make every consumer
# clone and build that toolkit at the same revision -- so those adapters stay out
# of the export set and the configure log says why (see each adapter's
# CMakeLists.txt for its own resolution order).
#
# The results are global properties, because the adapters decide inside their own
# subdirectory while the install rules run at the top level. A property rather
# than a cache entry: a cache value cannot hold the newlines a snippet of
# find_dependency() calls needs, and nothing here has to survive the configure
# pass that produced it.
# Records one adapter's install/export decision and prints it. `dependency` is
# the config-file snippet that re-creates the toolkit's imported target before
# plotTargets.cmake is included; it is empty when the target is not exported.
function(plot_record_adapter_export target exportable reason dependency)
if(NOT exportable)
message(STATUS "plot: not exporting ${target}: ${reason}")
elseif(NOT PLOT_INSTALL)
message(STATUS "plot: not exporting ${target}: PLOT_INSTALL is OFF")
else()
set_property(GLOBAL APPEND PROPERTY PLOT_ADAPTER_EXPORT_TARGETS "${target}")
# Read-modify-write, not APPEND: the snippets are one block of C++-style
# lines that must be substituted into one file, and a property read back is a
# list -- APPEND would join them with the separator ';' instead of a newline.
get_property(plot_dependencies GLOBAL PROPERTY PLOT_ADAPTER_DEPENDENCIES)
set_property(GLOBAL PROPERTY PLOT_ADAPTER_DEPENDENCIES "${plot_dependencies}${dependency}")
message(STATUS "plot: exporting ${target}: ${reason}")
endif()
endfunction()
# Apply the project warning set to a first-party target. Warnings are never
# suppressed per file.
function(plot_apply_warnings target)
if(MSVC)
target_compile_options(${target} PRIVATE /W4 /permissive-)
else()
target_compile_options(${target} PRIVATE
-Wall
-Wextra
-Wpedantic
-Wconversion
-Wshadow)
endif()
endfunction()
# Resolves GLFW once for every target that needs it -- the GLFW host adapter and
# the Dear ImGui backend both do. Discovery follows the design spec's order:
# find_package first, pkg-config next, FetchContent only when neither can supply
# GLFW (the last resort, because it needs the network). The result is cached in
# PLOT_GLFW_DEP, so a build with several adapters enabled discovers -- and at
# worst fetches -- exactly one copy. Sets <out_var> to the target to link.
#
# PLOT_GLFW_DEP_ORIGIN records which of the three supplied it, because only the
# first two can be promised to a consumer of an installed adapter (see
# plot_record_adapter_export()).
function(plot_find_glfw out_var)
if(NOT DEFINED PLOT_GLFW_DEP)
set(plot_glfw_dep "")
set(plot_glfw_origin "")
find_package(glfw3 3.3 QUIET)
if(TARGET glfw)
set(plot_glfw_dep glfw)
set(plot_glfw_origin find_package)
elseif(TARGET glfw3::glfw3)
set(plot_glfw_dep glfw3::glfw3)
set(plot_glfw_origin find_package)
endif()
if(NOT plot_glfw_dep)
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(GLFW_PC QUIET IMPORTED_TARGET glfw3)
if(TARGET PkgConfig::GLFW_PC)
set(plot_glfw_dep PkgConfig::GLFW_PC)
set(plot_glfw_origin pkg-config)
endif()
endif()
endif()
if(NOT plot_glfw_dep)
message(STATUS "plot: glfw3 not found via find_package or pkg-config; fetching it")
include(FetchContent)
set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(GLFW_INSTALL OFF CACHE BOOL "" FORCE)
FetchContent_Declare(glfw
GIT_REPOSITORY https://github.com/glfw/glfw.git
GIT_TAG 3.4
GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(glfw)
set(plot_glfw_dep glfw)
set(plot_glfw_origin FetchContent)
endif()
set(PLOT_GLFW_DEP "${plot_glfw_dep}" CACHE INTERNAL
"GLFW target the plot adapters link against")
set(PLOT_GLFW_DEP_ORIGIN "${plot_glfw_origin}" CACHE INTERNAL
"How plot resolved GLFW: find_package, pkg-config or FetchContent")
message(STATUS "plot: GLFW adapter dependency: ${plot_glfw_dep}")
endif()
set(${out_var} "${PLOT_GLFW_DEP}" PARENT_SCOPE)
endfunction()
# The find_dependency() snippet that re-creates an exported adapter's toolkit
# target on a consumer's machine, for the toolkits a config file can re-find.
# Adapters call these when they record their export decision; the snippets are
# substituted into cmake/plotConfig.cmake.in.
function(plot_glfw_dependency_snippet out_var)
if(PLOT_GLFW_DEP_ORIGIN STREQUAL "find_package")
set(${out_var} "find_dependency(glfw3 3.3)\n" PARENT_SCOPE)
else()
set(${out_var}
"find_dependency(PkgConfig)\npkg_check_modules(GLFW_PC REQUIRED IMPORTED_TARGET glfw3)\n"
PARENT_SCOPE)
endif()
endfunction()