Files
musrfit/cmake/MusrfitCheckROOT7.cmake
suter_aandClaude Sonnet 5 4302b40d9e cmake: don't let a stale cache poison the ROOT7 API compile check.
check_cxx_source_compiles() caches MUSRVIEW_HAS_ROOT7_API under a plain
variable name with no dependency tracking, so a failure recorded once
(e.g. against an older ROOT built into the same build/ directory) kept
being reported as missing on every later reconfigure even after ROOT was
fine. Unset both cache entries before the check so it always re-runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 16:46:42 +02:00

314 lines
14 KiB
CMake

#--- MusrfitCheckROOT7.cmake --------------------------------------------------
#
# Config time checks for everything musrview needs from ROOT.
#
# Since musrfit 1.13 musrview is built on the ROOT7 web graphics: the C++ side
# only holds the data and pushes it as JSON through an ROOT::RWebWindow
# websocket to a JSROOT page which does the rendering. This requires quite a
# few ROOT build options to be enabled (root7, webgui, http, ...) which are
# *not* all on by default in every ROOT installation. Without them musrview
# either does not compile at all or, worse, compiles and then fails at run time
# with a blank window. Hence check all of it here, at config time, and tell the
# user precisely what is missing and how to fix it.
#
# Expects find_package(ROOT ...) to have been called already.
#
# Sets (cached, for the config summary and for the sources):
# MUSRVIEW_JSROOT_DIR - the JSROOT directory of the ROOT installation
# MUSRVIEW_HAS_QT6WEB - ROOT provides the embedded Qt6 web display
# MUSRVIEW_HAS_IMAGE_EXPORT- ROOT provides libASImage (png/gif/jpg export)
#
#------------------------------------------------------------------------------
include(CheckCXXSourceCompiles)
if (NOT ROOT_FOUND)
message(FATAL_ERROR "MusrfitCheckROOT7: find_package(ROOT) has to be called first.")
endif ()
#--- collect all problems first, so that the user gets the complete list with
#--- a single cmake run instead of one error per run --------------------------
set(_musrview_missing "")
#--- ROOT_BINDIR is set by ROOTConfig.cmake, but be defensive about it since it
#--- is only used for the messages and for locating the JSROOT directory ------
if (NOT ROOT_BINDIR)
list(GET ROOT_INCLUDE_DIRS 0 _root_incdir)
get_filename_component(ROOT_BINDIR "${_root_incdir}/../bin" REALPATH)
endif ()
#--- cmake does not re-wrap indented lines, hence the texts handed to
#--- musrview_missing() are wrapped by hand below ----------------------------
macro(musrview_missing msg)
string(APPEND _musrview_missing " * ${msg}\n")
endmacro()
#------------------------------------------------------------------------------
# 1) the ROOT build options musrview relies on
#
# ROOTConfig.cmake exports every enabled ROOT build option as
# ROOT_<option>_FOUND, which is exactly what 'root-config --features' lists.
#------------------------------------------------------------------------------
if (NOT ROOT_root7_FOUND)
musrview_missing("ROOT was built without '-Droot7=ON' (no ROOT7 classes at all).")
endif ()
if (NOT ROOT_webgui_FOUND)
musrview_missing("ROOT was built without '-Dwebgui=ON' (no RWebWindow/web display).")
endif ()
if (NOT ROOT_http_FOUND)
musrview_missing("ROOT was built without '-Dhttp=ON'. RWebWindow serves its\n\
page and its websocket through THttpServer, hence musrview cannot work\n\
without it.")
endif ()
#------------------------------------------------------------------------------
# 2) the ROOT libraries musrview links against
#
# ROOTConfig-targets.cmake only defines the targets which this particular ROOT
# build actually produced, i.e. a missing target is a reliable statement about
# the ROOT installation, not about cmake.
#------------------------------------------------------------------------------
foreach (_root7_target ROOT::ROOTWebDisplay ROOT::RHTTP)
if (NOT TARGET ${_root7_target})
musrview_missing("the ROOT library ${_root7_target} is missing.")
endif ()
endforeach ()
#------------------------------------------------------------------------------
# 3) the ROOT7 headers musrview includes
#------------------------------------------------------------------------------
foreach (_root7_header ROOT/RWebWindow.hxx ROOT/RWebWindowsManager.hxx)
string(REGEX REPLACE "[/.]" "_" _root7_header_var "MUSRVIEW_HEADER_${_root7_header}")
find_file(${_root7_header_var}
NAMES ${_root7_header}
HINTS ${ROOT_INCLUDE_DIRS}
NO_DEFAULT_PATH
)
mark_as_advanced(${_root7_header_var})
if (NOT ${_root7_header_var})
musrview_missing("the header <${_root7_header}> is not part of this ROOT\n\
installation.")
endif ()
endforeach ()
#------------------------------------------------------------------------------
# 4) the ROOT7 API musrview uses
#
# Checking the headers/libraries is not enough: RWebWindow lived in
# ROOT::Experimental until ROOT 6.32 and moved to the plain ROOT namespace
# afterwards, which is what PMusrWebCanvas/musrview use. Compile the actual
# calls, so that an API change is caught here and not in the middle of the
# build. CMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY keeps this a compile-only
# check, i.e. no ROOT libraries have to be dragged in for it.
#------------------------------------------------------------------------------
set(MUSRVIEW_ROOT7_API_TEST_SRC "
#include <memory>
#include <string>
#include <TBufferJSON.h>
#include <TList.h>
#include <ROOT/RWebWindow.hxx>
#include <ROOT/RWebWindowsManager.hxx>
int main() {
@MUSRVIEW_NS@RWebWindowsManager::SetSingleConnMode(false);
std::shared_ptr<@MUSRVIEW_NS@RWebWindow> win = @MUSRVIEW_NS@RWebWindow::Create();
win->SetDefaultPage(std::string(\"file:musrview.html\"));
win->SetGeometry(800, 600);
win->SetConnLimit(1);
win->SetConnectCallBack([](unsigned) {});
win->SetDisconnectCallBack([](unsigned) {});
win->SetDataCallBack([](unsigned, const std::string &) {});
win->Show(std::string(\"qt6\"));
win->Send(1, std::string(\"PLOT:\"));
win->CloseConnections();
TList list;
TString json = TBufferJSON::ToJSON(&list, TBufferJSON::kNoSpaces + TBufferJSON::kSameSuppression);
return json.Length() > 0 ? 0 : 1;
}
")
set(CMAKE_REQUIRED_INCLUDES ${ROOT_INCLUDE_DIRS})
set(CMAKE_REQUIRED_FLAGS "${ROOT_CXX_FLAGS}")
set(CMAKE_REQUIRED_QUIET TRUE)
set(_musrview_try_compile_type_save "${CMAKE_TRY_COMPILE_TARGET_TYPE}")
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
#--- check_cxx_source_compiles() caches its result under a plain variable
#--- name with no dependency tracking: if it once failed (e.g. against an
#--- older ROOT in this same build directory) it would keep reporting that
#--- stale failure on every later reconfigure, even after ROOT got fixed,
#--- since neither the test source nor ROOT_VERSION are part of the cache
#--- key. Unset both results first so the compile check always re-runs. ------
unset(MUSRVIEW_HAS_ROOT7_API CACHE)
unset(MUSRVIEW_HAS_ROOT7_API_EXPERIMENTAL CACHE)
set(MUSRVIEW_NS "ROOT::")
string(CONFIGURE "${MUSRVIEW_ROOT7_API_TEST_SRC}" _musrview_api_src @ONLY)
check_cxx_source_compiles("${_musrview_api_src}" MUSRVIEW_HAS_ROOT7_API)
if (NOT MUSRVIEW_HAS_ROOT7_API)
#--- was it only the namespace, i.e. a ROOT which is too old? --------------
set(MUSRVIEW_NS "ROOT::Experimental::")
string(CONFIGURE "${MUSRVIEW_ROOT7_API_TEST_SRC}" _musrview_api_src_exp @ONLY)
check_cxx_source_compiles("${_musrview_api_src_exp}" MUSRVIEW_HAS_ROOT7_API_EXPERIMENTAL)
if (MUSRVIEW_HAS_ROOT7_API_EXPERIMENTAL)
musrview_missing("this ROOT (${ROOT_VERSION}) still has RWebWindow in the\n\
'ROOT::Experimental' namespace. musrview needs the final\n\
ROOT::RWebWindow, i.e. ROOT 6.32 or newer.")
else ()
musrview_missing("the ROOT7 web window API (ROOT::RWebWindow::Create(),\n\
SetDefaultPage(), SetGeometry(), SetConnLimit(), SetDataCallBack(),\n\
Show(), Send(), ROOT::RWebWindowsManager::SetSingleConnMode(),\n\
TBufferJSON::ToJSON()) does not compile against this ROOT\n\
(${ROOT_VERSION}). The cmake configure log holds the compiler output.")
endif ()
endif ()
set(CMAKE_TRY_COMPILE_TARGET_TYPE "${_musrview_try_compile_type_save}")
unset(_musrview_try_compile_type_save)
unset(CMAKE_REQUIRED_INCLUDES)
unset(CMAKE_REQUIRED_FLAGS)
unset(CMAKE_REQUIRED_QUIET)
unset(MUSRVIEW_NS)
unset(MUSRVIEW_ROOT7_API_TEST_SRC)
unset(_musrview_api_src)
unset(_musrview_api_src_exp)
#------------------------------------------------------------------------------
# 5) JSROOT
#
# The rendering is done by the JSROOT which ships with ROOT. src/musrview_web/
# musrview.html imports the bare module specifiers 'jsroot/core',
# 'jsroot/draw' and 'jsroot/webwindow' and relies on THttpServer replacing the
# '<!--jsroot_importmap-->' marker in the served page by the import map which
# resolves them. Check both: the JSROOT modules on disk and the import map
# support of the installed libRHTTP.
#------------------------------------------------------------------------------
find_path(MUSRVIEW_JSROOT_DIR
NAMES modules/core.mjs
HINTS $ENV{JSROOTSYS}
${ROOT_BINDIR}/../js
${ROOT_BINDIR}/../share/root/js
$ENV{ROOTSYS}/js
NO_DEFAULT_PATH
DOC "JSROOT directory of the ROOT installation used by musrview"
)
mark_as_advanced(MUSRVIEW_JSROOT_DIR)
if (NOT MUSRVIEW_JSROOT_DIR)
musrview_missing("the JSROOT of this ROOT installation could not be found\n\
(no 'js/modules/core.mjs'). musrview is rendered by JSROOT, hence it is\n\
needed. If it sits somewhere else, point JSROOTSYS or\n\
-DMUSRVIEW_JSROOT_DIR=<dir> at it.")
else ()
foreach (_jsroot_module core.mjs draw.mjs webwindow.mjs)
if (NOT EXISTS "${MUSRVIEW_JSROOT_DIR}/modules/${_jsroot_module}")
musrview_missing("the JSROOT module '${_jsroot_module}' is missing in\n\
${MUSRVIEW_JSROOT_DIR}/modules, but musrview.html imports it.")
endif ()
endforeach ()
endif ()
#--- The import map itself is generated by THttpServer, i.e. by libRHTTP.
#--- Two independent signals that this ROOT supports it: the pages ROOT itself
#--- serves carry the very same marker, and libRHTTP holds the marker together
#--- with the 'jsroot/...' module specifiers it maps (file(STRINGS ...) also
#--- works on binaries). Either one is good enough, complain only if neither is
#--- there - which is much more robust than deducing it from the ROOT version.
set(_musrview_has_importmap FALSE)
#--- signal 1: a page shipped by ROOT which is served through THttpServer ----
if (MUSRVIEW_JSROOT_DIR AND EXISTS "${MUSRVIEW_JSROOT_DIR}/files/online.htm")
file(STRINGS "${MUSRVIEW_JSROOT_DIR}/files/online.htm" _jsroot_marker
REGEX "jsroot_importmap")
if (_jsroot_marker)
set(_musrview_has_importmap TRUE)
endif ()
endif ()
#--- signal 2: libRHTTP itself ----------------------------------------------
if (NOT _musrview_has_importmap AND TARGET ROOT::RHTTP)
set(_rhttp_lib "")
foreach (_prop IMPORTED_LOCATION IMPORTED_LOCATION_RELEASE IMPORTED_LOCATION_NOCONFIG)
if (NOT _rhttp_lib)
get_target_property(_rhttp_lib ROOT::RHTTP ${_prop})
if (NOT _rhttp_lib)
set(_rhttp_lib "")
endif ()
endif ()
endforeach ()
if (_rhttp_lib AND EXISTS "${_rhttp_lib}")
file(STRINGS "${_rhttp_lib}" _rhttp_importmap REGEX "jsroot_importmap")
file(STRINGS "${_rhttp_lib}" _rhttp_webwindow REGEX "^jsroot/webwindow$")
if (_rhttp_importmap AND _rhttp_webwindow)
set(_musrview_has_importmap TRUE)
endif ()
else ()
#--- nothing to look at, do not cry wolf ---------------------------------
set(_musrview_has_importmap TRUE)
endif ()
endif ()
if (NOT _musrview_has_importmap)
musrview_missing("the THttpServer of this ROOT does not provide the JSROOT\n\
import map ('<!--jsroot_importmap-->' and the 'jsroot/...' module\n\
specifiers) which src/musrview_web/musrview.html relies on. ROOT 6.32 or\n\
newer is required.")
endif ()
#------------------------------------------------------------------------------
# 6) bail out if anything is missing
#------------------------------------------------------------------------------
if (_musrview_missing)
get_filename_component(_root_prefix "${ROOT_BINDIR}/.." REALPATH)
message(FATAL_ERROR
"musrview is built on the ROOT7 web graphics, but the ROOT installation\n"
" ${_root_prefix} (version ${ROOT_VERSION})\n"
"does not provide everything it needs:\n"
"${_musrview_missing}"
"Please rebuild ROOT with at least\n"
" -Droot7=ON -Dwebgui=ON -Dhttp=ON\n"
"and, for the embedded musrview window, in addition -Dqt6web=ON.\n"
"If more than one ROOT is installed, check that ROOTSYS/CMAKE_PREFIX_PATH point to the right one.")
endif ()
#------------------------------------------------------------------------------
# 7) optional, i.e. musrview works without it but with reduced functionality
#------------------------------------------------------------------------------
#--- embedded window: '--embedded' is musrview's default, without the Qt6 web
#--- display of ROOT it falls back to the standard browser of the system. -----
if (TARGET ROOT::ROOTQt6WebDisplay AND ROOT_qt6web_FOUND)
set(MUSRVIEW_HAS_QT6WEB TRUE CACHE INTERNAL "ROOT provides the embedded Qt6 web display")
else ()
set(MUSRVIEW_HAS_QT6WEB FALSE CACHE INTERNAL "ROOT provides the embedded Qt6 web display")
message(WARNING
"ROOT is built without the Qt6 web display (-Dqt6web=ON), hence musrview cannot "
"show its embedded window and will use the standard browser of the system instead. "
"Everything else works as usual.")
endif ()
#--- graphics export: musrview --png/--gif/--jpg draw into a real TCanvas in
#--- batch mode, which needs libASImage. eps/pdf/svg/root do not. The
#--- musrview_check tests compare png output, i.e. they need it as well. ------
if (TARGET ROOT::ASImage AND ROOT_asimage_FOUND)
set(MUSRVIEW_HAS_IMAGE_EXPORT TRUE CACHE INTERNAL "ROOT provides libASImage")
else ()
set(MUSRVIEW_HAS_IMAGE_EXPORT FALSE CACHE INTERNAL "ROOT provides libASImage")
message(WARNING
"ROOT is built without libASImage (-Dasimage=ON), hence 'musrview --png/--gif/--jpg' "
"will not produce any output (eps/pdf/svg/root are fine) and the musrview_check "
"tests will fail.")
endif ()
unset(_musrview_missing)
unset(_musrview_has_importmap)
unset(_rhttp_lib)