Breaking: Series::gather emits x relative to x0() and takes a GatherMode; consumers reconstruct absolute x in f64 before mapping (series.hpp documents the contract). Bugs, each pinned by a fail-before regression test: - render: f32 staging quantised epoch-time x into 128 px columns; every data-space draw now stages to_space(x) - to_space(origin) with the origin folded into the offset uniform in f64 (epoch/zero-origin twins rasterise identically) - ticks: major test was ULP-fragile and blanked epoch axes; majors are an integer-index test and are never truncated by the minor cap - gather: LOD envelope emitted buckets straddling [begin,end); ceil/floor bucket edges + raw lead/trail spans - live follow: log x axes held a linear span and zoomed every frame; span and offset now live in axis space (plot + both figure link-group paths) - input: a zero-notch wheel disarmed auto_scale on every axis; dy==0 pans x, factor == 1 / inf refused in wheel and drag zoom - render: the single text flush painted legend names over the crosshair readout panel; two batches, under flushed before the panel - data: NaN dropped from min/max unless it seeded the fold; non-finite samples are now excluded from every aggregate and from cached bounds Robustness: viewport normalisation (no clamp UB), non-finite range refusal, pyramid level cap at 57 (no shift/div UB through public gather), rounded ring capacity documented + pre-sized, GL blend func and UNPACK_ALIGNMENT save/restore, Renderer::release_gl() teardown, u64 buffer epochs, annotation captions clamped, button-aware GLFW row capture, cached figure row rects (no per-event layout), ruler-pair measurement refused, span-nonfinite guards in axis_ends. Features: XY series (sorted x, binary-search neighbours/snap, LOD over sample index); StreamSeries::freeze(); ArraySeries::push(); per-trace dash and markers (scatter at width 0) with auto or literal stride; GatherMode Envelope/Nearest/Mean via bucket_sum/sumsq; Style::line_aa_px; per-glyph text colour (readout values in trace colour); wheel-dx x pan; arrow-key pan; Escape cancels a drag and restores captured ranges; modifier-masked box zoom (Shift both, Ctrl x, Alt y); double-click reset via PointerEvent::time_s; ImGui key repeat unified; pinch gestures on Qt and GTK; pixelDelta wheel path and DevicePixelRatioChange handling; GNUInstallDirs, SOVERSION, SameMinorVersion, generated plot/version.hpp, relocatable plot.pc, licence install, FILES_MATCHING, GitHub Actions CI (debug/coverage/asan). Suite 283 -> 350 cases, 0 failed; coverage 93.9% (3730/3971) over 24 files, floor 80/90 held; ASAN preset suite green; all four demos run headless (the GTK scripted-resize check needs a window manager and fails identically on the parent commit); readme_main.cpp committed and compiles with the README command; pkg-config and find_package consumption verified from a relocated install prefix.
220 lines
11 KiB
CMake
220 lines
11 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)
|
|
# 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_<CONFIG>) 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 <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()
|