cmake: check at config time that ROOT provides all the ROOT7 features musrview needs.

Since musrview is built on the ROOT7 web graphics it needs a fair number of
ROOT build options which are not all on by default. Without them musrview
either does not compile at all or, worse, compiles and then shows a blank
window at run time. The only check so far was for the ROOT::ROOTWebDisplay
target.

The new cmake/MusrfitCheckROOT7.cmake collects all problems and reports them
in one go, together with the ROOT cmake flags needed to fix them:
 - the ROOT build options root7, webgui and http (ROOT_<option>_FOUND),
 - the libraries ROOT::ROOTWebDisplay and ROOT::RHTTP,
 - the headers ROOT/RWebWindow.hxx and ROOT/RWebWindowsManager.hxx,
 - the API itself, through a compile-only check of the calls musrview and
   PMusrWebCanvas make. If that fails it is retried in the ROOT::Experimental
   namespace, which tells the user that the ROOT is older than 6.32 instead of
   leaving them with a compiler error,
 - JSROOT: the js directory (overridable with -DMUSRVIEW_JSROOT_DIR=<dir>), the
   modules musrview.html imports and the '<!--jsroot_importmap-->' support of
   THttpServer which resolves the 'jsroot/...' specifiers.

Not fatal, but warned about and shown in the config summary:
 - MUSRVIEW_HAS_QT6WEB: without ROOT's qt6web musrview falls back from its
   embedded window to the standard browser of the system,
 - MUSRVIEW_HAS_IMAGE_EXPORT: without libASImage 'musrview --png/--gif/--jpg'
   writes nothing. In that case the musrview_check tests, which compare
   rendered PNGs, are no longer registered - they could then only fail for a
   reason that has nothing to do with musrfit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Ck7v5F7KjinfC2EyE7K3C
This commit is contained in:
suter_aandClaude Opus 5 committed 2026-09-11 16:26:48 +02:00
1 parent 2cb76879ff
commit ff8fb9cf70
4 files changed
+341 -13

No files matched your search

+7 -3
View File
@@ -115,9 +115,13 @@ The central data model and control flow (all in `src/classes/` with headers in `
need no browser.
- The web assets are installed to `<prefix>/share/musrfit/musrview`; set `MUSRFIT_WEB_DIR` to run
from the source tree, and `MUSRVIEW_DEBUG=1` to trace the websocket traffic.
- musrview needs a ROOT built with `-Droot7=ON -Dwebgui=ON`, plus `-Dqt6web=ON` for the embedded
window (`--embedded`, the default). `--browser` uses the system browser, `--browser=server`
only prints the URL.
- musrview needs a ROOT built with `-Droot7=ON -Dwebgui=ON -Dhttp=ON`, plus `-Dqt6web=ON` for
the embedded window (`--embedded`, the default). `--browser` uses the system browser,
`--browser=server` only prints the URL. All of that (build options, `ROOT::ROOTWebDisplay`/
`ROOT::RHTTP`, the ROOT7 headers, the `ROOT::RWebWindow` API itself via a compile check,
JSROOT and its import map) is verified at config time by `cmake/MusrfitCheckROOT7.cmake`,
which also sets `MUSRVIEW_HAS_QT6WEB` / `MUSRVIEW_HAS_IMAGE_EXPORT` (the latter gates the
`musrview_check` tests, since they compare rendered PNGs and thus need ROOT's libASImage).
- **`PFourier`** / **`PFourierCanvas`** / **`PPrepFourier`** handle the Fourier transform and its
(still TCanvas based) display, used by `musrview`/`musrFT`.
- **`PMsr2Data`** implements `msr2data`'s logic for generating follow-up/global msr files from a
+21 -10
View File
@@ -101,16 +101,6 @@ set(MUSRFIT_CXX_FLAGS_NO_ROOT "${CMAKE_CXX_FLAGS}")
find_package(ROOT 6.18 REQUIRED COMPONENTS Gui MathMore Minuit2 XMLParser)
#--- musrview is based on the ROOT7 web graphics, which needs a ROOT built
#--- with -Dwebgui=ON (and -Droot7=ON). Without it there is no RWebWindow and
#--- musrview cannot be built. ------------------------------------------------
if (NOT TARGET ROOT::ROOTWebDisplay)
message(FATAL_ERROR
"musrview needs the ROOT web display (RWebWindow), which this ROOT "
"installation does not provide. Please rebuild ROOT with "
"-Droot7=ON -Dwebgui=ON (and -Dqt6web=ON for the embedded window).")
endif ()
#--- Deliberately NOT 'include(${ROOT_USE_FILE})' here. That file
#--- - overwrites CMAKE_CXX_STANDARD with ROOT's value, silently discarding
#--- the setting made at the top of this file,
@@ -143,6 +133,13 @@ add_definitions(${ROOT_DEFINITIONS})
string(APPEND CMAKE_CXX_FLAGS " ${ROOT_CXX_FLAGS}")
string(APPEND CMAKE_C_FLAGS " ${ROOT_C_FLAGS}")
#--- musrview is built on the ROOT7 web graphics, which needs a ROOT providing
#--- quite a bit more than the plain 'find_package(ROOT)' above checks for
#--- (root7, webgui, http, RWebWindow, JSROOT, ...). All of that is verified
#--- here, at config time. Done *after* the flags/standard above, so that the
#--- compile checks in there see exactly the same setup as the real build. ----
include(MusrfitCheckROOT7)
#--- the next check is need to set a flag, since root 6.24 (minuit2) breaks
#--- the backwards compatibility. ---------------------------------------------
if (ROOT_VERSION VERSION_LESS "6.23")
@@ -548,6 +545,20 @@ if (DummyUserFcn)
message(" PDummyUserFcn : yes")
endif (DummyUserFcn)
message("")
message(" musrview (ROOT7 web graphics):")
if (MUSRVIEW_HAS_QT6WEB)
message(" embedded window (ROOT qt6web) : yes")
else ()
message(" embedded window (ROOT qt6web) : no, will use the standard browser")
endif ()
if (MUSRVIEW_HAS_IMAGE_EXPORT)
message(" graphics export png/gif/jpg : yes")
else ()
message(" graphics export png/gif/jpg : no, ROOT has no libASImage")
endif ()
message(" JSROOT : ${MUSRVIEW_JSROOT_DIR}")
if (qt_based_tools)
if (Qt6Core_FOUND)
message("")
+304
View File
@@ -0,0 +1,304 @@
#--- 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)
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)
+9
View File
@@ -1,5 +1,14 @@
#--- musrview PNG integration tests -------------------------------------------
#--- these tests compare the PNG which 'musrview --png' renders. Without
#--- libASImage ROOT cannot write a PNG at all (see cmake/MusrfitCheckROOT7.cmake,
#--- which warns about it at config time), hence do not register tests which
#--- could then only fail for a reason that has nothing to do with musrfit. ---
if (NOT MUSRVIEW_HAS_IMAGE_EXPORT)
message(STATUS "musrview_check: ROOT has no libASImage, skipping the musrview-* tests")
return ()
endif ()
find_package(Python3 REQUIRED COMPONENTS Interpreter)
#--- musrview_check.py needs Pillow/numpy for the pixel comparison. CMake's