# 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() 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) # The optional runtime-font backend: plot::text::TrueTypeFont, which bakes a real # TrueType/OpenType file into a fixed-cell atlas at run time (see # include/plot/text/true_type_font.hpp). It is the one target that compiles the # vendored third_party/stb_truetype.h, so it is opt-in and separable -- the core # library, its public headers and its tests build without it -- and its default # follows the vendored header's presence: a tree that has the file wants the # feature, and one that does not cannot have it. Asking for it explicitly without # the header is an error rather than a silent downgrade, because the option is # then the only thing that would have said so. The top-level CMakeLists.txt adds # the target itself; this file only decides whether it exists. if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/stb_truetype.h") set(PLOT_VENDORED_STB_TRUETYPE TRUE) else() set(PLOT_VENDORED_STB_TRUETYPE FALSE) endif() option(PLOT_BUILD_TTF_FONT "Build the optional plot::text::TrueTypeFont backend (vendored stb_truetype)" ${PLOT_VENDORED_STB_TRUETYPE}) if(PLOT_BUILD_TTF_FONT AND NOT PLOT_VENDORED_STB_TRUETYPE) message(FATAL_ERROR "plot: PLOT_BUILD_TTF_FONT=ON needs third_party/stb_truetype.h, which this tree does " "not have. Restore it -- include/plot/text/true_type_font.hpp records the version and " "the source URL it came from -- or configure with -DPLOT_BUILD_TTF_FONT=OFF.") endif() 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) # The instrumented tree is one whose *effective* flags carry --coverage, and the # base CMAKE_CXX_FLAGS is only part of those: the active configuration's own # profile (CMAKE_CXX_FLAGS_) is applied on top, and --coverage is # equally at home in either. The linux-coverage preset sets the base; a user who # sets it on the Debug profile instead gets exactly the same instrumented tree, # which the old base-only string match rejected. Matching the two together is # still just an option check on the configuration profile -- the guard does not # try to read the compiler's mind, only the flags this build will pass it. string(TOUPPER "${CMAKE_BUILD_TYPE}" plot_coverage_config) set(plot_coverage_flags "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${plot_coverage_config}}") if(PLOT_COVERAGE AND NOT plot_coverage_flags MATCHES "--coverage") message(FATAL_ERROR "plot: PLOT_COVERAGE=ON needs a --coverage build (CMAKE_CXX_FLAGS is " "\"${CMAKE_CXX_FLAGS}\" and CMAKE_CXX_FLAGS_${plot_coverage_config} is " "\"${CMAKE_CXX_FLAGS_${plot_coverage_config}}\"). " "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 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()