From 3a6e79d1a6240696d1e4279c03264d402f48974e Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Wed, 8 Oct 2025 15:07:39 +0200 Subject: [PATCH 1/8] [geom] Add a missing include and a forward declaration in TGeo. --- geom/geom/inc/TGeoTypedefs.h | 8 ++++++++ geom/geom/inc/TGeoVoxelGrid.h | 1 + 2 files changed, 9 insertions(+) diff --git a/geom/geom/inc/TGeoTypedefs.h b/geom/geom/inc/TGeoTypedefs.h index c29f33febca72..a1f468e26789d 100644 --- a/geom/geom/inc/TGeoTypedefs.h +++ b/geom/geom/inc/TGeoTypedefs.h @@ -15,6 +15,14 @@ /// Typedefs used by the geometry group #include +namespace ROOT::Geom { +struct Vertex_t; +} + +namespace ROOT::Geom { +struct Vertex_t; +} + namespace Tessellated { using Vertex_t = ROOT::Geom::Vertex_t; diff --git a/geom/geom/inc/TGeoVoxelGrid.h b/geom/geom/inc/TGeoVoxelGrid.h index 98021630a8b62..5d7401eaef6e2 100644 --- a/geom/geom/inc/TGeoVoxelGrid.h +++ b/geom/geom/inc/TGeoVoxelGrid.h @@ -15,6 +15,7 @@ #include #include #include +#include // a simple structure to encode voxel indices, to address // individual voxels in the 3D grid. From cdbbd9243d76a4b55faf86b7fc8a2184c0527459 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Thu, 9 Oct 2025 11:53:46 +0200 Subject: [PATCH 2/8] [Core] Use more helpful error message in TThreadExecutor when imt=Off. RTaskArena uses a message pointing to imt=Off, whereas TThreadExecutor was pointing to the name of the ROOT-internal macro. Here, the same error message is used. --- core/imt/inc/ROOT/TThreadExecutor.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/imt/inc/ROOT/TThreadExecutor.hxx b/core/imt/inc/ROOT/TThreadExecutor.hxx index 7ab57cdce6628..dd1494f0c1cf1 100644 --- a/core/imt/inc/ROOT/TThreadExecutor.hxx +++ b/core/imt/inc/ROOT/TThreadExecutor.hxx @@ -18,7 +18,7 @@ #ifndef R__USE_IMT // No need to error out for dictionaries. # if !defined(__ROOTCLING__) && !defined(G__DICTIONARY) -# error "Cannot use ROOT::TThreadExecutor without defining R__USE_IMT." +#error "Cannot use ROOT::TThreadExecutor when build option imt=Off." # endif #else From 610794c4a6557bcc59feda8c48ac2442d8866f25 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Wed, 2 Sep 2026 16:34:52 +0200 Subject: [PATCH 3/8] [CMake] Add a new argument for ROOT_INSTALL_HEADERS. Instead of globbing in inc/ and unconditionally installing all headers, provide a mode where all headers which are supposed to be installed have to be listed explicitly. In this way, headers that correspond to disabled features can be left out of modules and install set. In the long run, these sets can be converted to CMake file sets, which will simplify associating them to targets. --- cmake/modules/RootMacros.cmake | 92 ++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/cmake/modules/RootMacros.cmake b/cmake/modules/RootMacros.cmake index 7d938f6883548..8e824685acb33 100644 --- a/cmake/modules/RootMacros.cmake +++ b/cmake/modules/RootMacros.cmake @@ -1223,60 +1223,69 @@ function(ROOT_FIND_DIRS_WITH_HEADERS result_dirs) endfunction() #--------------------------------------------------------------------------------------------------- -#---ROOT_INSTALL_HEADERS([dir1 dir2 ...] [FILTER ]) -# Glob for headers in the folder where this target is defined, and install them in -# /include +#---ROOT_INSTALL_HEADERS([dir1 dir2 ...] [FILTER ] [HEADERS ...]) +# Declare the install command for headers and copy them into /include. +# This function supports two modes to build the list of headers: +# - [New] If headers are passed explicitly using HEADERS ..., install only these +# - [Old] Otherwise, glob in the specified folders or where this target is defined #--------------------------------------------------------------------------------------------------- function(ROOT_INSTALL_HEADERS) - CMAKE_PARSE_ARGUMENTS(ARG "OPTIONS" "" "FILTER" ${ARGN}) + CMAKE_PARSE_ARGUMENTS(ARG "OPTIONS" "" "FILTER;HEADERS" ${ARGN}) if (${ARG_OPTIONS}) message(FATAL_ERROR "ROOT_INSTALL_HEADERS no longer supports the OPTIONS argument. Rewrite using the FILTER argument.") endif() - ROOT_FIND_DIRS_WITH_HEADERS(dirs ${ARG_UNPARSED_ARGUMENTS}) - set (filter "LinkDef") - set (options REGEX "LinkDef" EXCLUDE) - foreach (f ${ARG_FILTER}) - set (filter "${filter}|${f}") - set (options ${options} REGEX "${f}" EXCLUDE) - endforeach() - set (filter "(${filter})") - set(include_files "") - foreach(d ${dirs}) - install(DIRECTORY ${d} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - COMPONENT headers - ${options}) - string(REGEX REPLACE "(.*)/$" "\\1" d ${d}) - ROOT_GLOB_FILES(globbed_files - RECURSE - RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} - FILTER ${filter} - ${d}/*.h ${d}/*.hxx ${d}/*.icc ) - list(APPEND include_files ${globbed_files}) - endforeach() - string(REPLACE ${CMAKE_SOURCE_DIR} "" target_name ${CMAKE_CURRENT_SOURCE_DIR}) - string(REPLACE / _ target_name "copy_header_${target_name}") - string(REGEX REPLACE "_$" "" target_name ${target_name}) + unset(include_files) + + if(ARG_HEADERS) + # Headers have been listed explicitly, find them one by one + foreach(regex ${ARG_FILTER} "LinkDef") + list(FILTER ARG_HEADERS EXCLUDE REGEX "${regex}") + endforeach() + foreach(header ${ARG_HEADERS}) + file(GLOB globbed_header ${header} */${header}) + if(globbed_header STREQUAL "") + message(SEND_ERROR "No header corresponding to ${header} found in ${CMAKE_CURRENT_SOURCE_DIR}") + endif() + list(APPEND include_files ${globbed_header}) + endforeach() + else() + # Glob across all include directories + ROOT_FIND_DIRS_WITH_HEADERS(dirs ${ARG_UNPARSED_ARGUMENTS}) + set (filter "LinkDef") + foreach (f ${ARG_FILTER}) + set (filter "${filter}|${f}") + endforeach() + set (filter "(${filter})") + foreach(d ${dirs}) + string(REGEX REPLACE "(.*)/$" "\\1" d ${d}) + ROOT_GLOB_FILES(globbed_files + RECURSE + FILTER ${filter} + ${d}/*.h ${d}/*.hxx ${d}/*.icc ) + list(APPEND include_files ${globbed_files}) + endforeach() + endif() # Register the files to be copied for each target directory (e.g. include/ include/ROOT include/v7/inc/ ...) list(REMOVE_DUPLICATES include_files) list(TRANSFORM include_files REPLACE "(.*)/[^/]*" "\\1/" OUTPUT_VARIABLE subdirs) list(REMOVE_DUPLICATES subdirs) foreach(subdir ${subdirs}) + string(REGEX REPLACE ".*/inc/" "" destination_subdir ${subdir}) + set(input_files ${include_files}) list(FILTER input_files INCLUDE REGEX "^${subdir}[^/]*$") - set(output_files ${input_files}) - string(REGEX REPLACE ".*/*inc/" "" destination ${subdir}) + install(FILES ${input_files} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${destination_subdir} COMPONENT headers) - list(TRANSFORM input_files PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/") - list(TRANSFORM output_files REPLACE ".*/" "${CMAKE_BINARY_DIR}/include/${destination}") - - set(destination destination_${destination}) + set(output_files ${input_files}) + list(TRANSFORM output_files REPLACE ".*/" "${CMAKE_BINARY_DIR}/include/${destination_subdir}") - set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_COPY_LISTS ${destination}) - set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_INPUT_${destination} ${input_files}) - set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_OUTPUT_${destination} ${output_files}) + set(destination_target_name destination_${destination_subdir}) + set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_COPY_LISTS ${destination_target_name}) + set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_INPUT_${destination_target_name} ${input_files}) + set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_OUTPUT_${destination_target_name} ${output_files}) endforeach() endfunction() @@ -1317,6 +1326,7 @@ endmacro() #--------------------------------------------------------------------------------------------------- #---ROOT_STANDARD_LIBRARY_PACKAGE(libname # [NO_INSTALL_HEADERS] : don't install headers for this package +# [NO_GLOB_HEADERS] : don't glob for headers, only install listed ones # [STAGE1] : use rootcling_stage1 for generating # HEADERS header1 header2 : relative header path as #included; pass -I to find them. If not specified, globbing for *.h is used # NODEPHEADERS header1 header2 : like HEADERS, but no dependency is generated @@ -1335,7 +1345,7 @@ endmacro() # ) #--------------------------------------------------------------------------------------------------- function(ROOT_STANDARD_LIBRARY_PACKAGE libname) - set(options NO_INSTALL_HEADERS STAGE1 NO_HEADERS NO_SOURCES OBJECT_LIBRARY NO_CXXMODULE) + set(options NO_INSTALL_HEADERS NO_GLOB_HEADERS STAGE1 NO_HEADERS NO_SOURCES OBJECT_LIBRARY NO_CXXMODULE) set(oneValueArgs LINKDEF) set(multiValueArgs DEPENDENCIES HEADERS NODEPHEADERS SOURCES BUILTINS LIBRARIES DICTIONARY_OPTIONS INSTALL_OPTIONS) CMAKE_PARSE_ARGUMENTS(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -1441,7 +1451,11 @@ function(ROOT_STANDARD_LIBRARY_PACKAGE libname) # Install headers if we have any headers and if the user didn't explicitly # disabled this. if (NOT ARG_NO_INSTALL_HEADERS OR ARG_NO_HEADERS) - ROOT_INSTALL_HEADERS(${ARG_INSTALL_OPTIONS}) + if(ARG_NO_GLOB_HEADERS) + ROOT_INSTALL_HEADERS(${ARG_INSTALL_OPTIONS} HEADERS ${ARG_HEADERS}) + else() + ROOT_INSTALL_HEADERS(${ARG_INSTALL_OPTIONS}) + endif() endif() endfunction() From 1e1a0013e93e6d45893de06908457b2b9bc44cfe Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Wed, 2 Sep 2026 17:12:11 +0200 Subject: [PATCH 4/8] [RDF] Stop installing ArrowDS and sqlite headers if they are off. By explicitly listing the headers to be installed instead of globbing for them, disabled features can be removed from the install set. --- tree/dataframe/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tree/dataframe/CMakeLists.txt b/tree/dataframe/CMakeLists.txt index a9d4b000922df..b16e21c7506b0 100644 --- a/tree/dataframe/CMakeLists.txt +++ b/tree/dataframe/CMakeLists.txt @@ -36,6 +36,7 @@ if (runtime_cxxmodules AND WIN32) endif() ROOT_STANDARD_LIBRARY_PACKAGE(ROOTDataFrame + NO_GLOB_HEADERS # Don't install RArrowDS and similar if they are off HEADERS ROOT/RCsvDS.hxx ROOT/RVecDS.hxx From acc607af48e5a1a25d063acd9a89f5a90c119eb1 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Wed, 2 Sep 2026 16:37:36 +0200 Subject: [PATCH 5/8] [ntuple] Only install headers that are active. When curl or daos are off, the corresponding headers are broken. Therefore, they should not be installed. --- tree/ntuple/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tree/ntuple/CMakeLists.txt b/tree/ntuple/CMakeLists.txt index 699d390a1a8fa..42376bbd7de4f 100644 --- a/tree/ntuple/CMakeLists.txt +++ b/tree/ntuple/CMakeLists.txt @@ -27,6 +27,7 @@ if(daos OR daos_mock) endif() ROOT_STANDARD_LIBRARY_PACKAGE(ROOTNTuple +NO_GLOB_HEADERS # Ensure that the optional headers above only get installed when enabled HEADERS ROOT/RCluster.hxx ROOT/RClusterPool.hxx From 0aa36bf9336db4497d9b14c8f81d56556c7035e5 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Wed, 2 Sep 2026 17:48:12 +0200 Subject: [PATCH 6/8] [ntuple] Add RField headers to set of installed headers. --- tree/ntuple/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tree/ntuple/CMakeLists.txt b/tree/ntuple/CMakeLists.txt index 42376bbd7de4f..29c7aff41ea4e 100644 --- a/tree/ntuple/CMakeLists.txt +++ b/tree/ntuple/CMakeLists.txt @@ -124,6 +124,15 @@ DEPENDENCIES ${ROOTNTuple_OPTIONAL_DEPENDENCIES} ) +# Non-standalone headers must be left out of the dictionary, so a dedicated install command is required. +ROOT_INSTALL_HEADERS(HEADERS + ROOT/RField/RFieldFundamental.hxx + ROOT/RField/RFieldProxiedCollection.hxx + ROOT/RField/RFieldRecord.hxx + ROOT/RField/RFieldSequenceContainer.hxx + ROOT/RField/RFieldSoA.hxx + ROOT/RField/RFieldSTLMisc.hxx) + target_link_libraries(ROOTNTuple PRIVATE xxHash::xxHash) # Enable RNTuple support for Intel DAOS From 108b497a5f7ae75f2c9c0ee45e07d0b21da24258 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 18 Mar 2025 10:38:33 +0100 Subject: [PATCH 7/8] [Post-install] Add a script to check headers for syntax errors. Add a script that checks all installed headers for syntax errors. This tests if they can be included standalone or if they rely on parasitic includes. Several subfolders or headers are skipped for now, since they either cannot be used standalone or depend on externals which are not necessarily installed. --- test/PostInstall/check-headers.sh | 47 +++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 test/PostInstall/check-headers.sh diff --git a/test/PostInstall/check-headers.sh b/test/PostInstall/check-headers.sh new file mode 100644 index 0000000000000..4104f3d7e0963 --- /dev/null +++ b/test/PostInstall/check-headers.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Adapted from the XRootD project with friendly permission from G. Amadio. +# +# This script checks that each installed ROOT header can be included individually +# without errors. The intention is to identify which headers may have missing +# includes, missing forward declarations, or missing header dependencies, that is, +# headers from ROOT which it includes, but were not installed by the install target. + +# We need to split CXXFLAGS +# shellcheck disable=SC2086 + +: "${INCLUDE_DIR:=${1}}" +: "${CXX:=$(${INCLUDE_DIR}/../bin/root-config --cxx || echo c++)}" +: "${CXXFLAGS:=-Wall -Wextra -Wno-unused-parameter -Wno-unused-const-variable}" +: "${NCPU:=$(getconf _NPROCESSORS_ONLN)}" +: "${CXXSTANDARD:=$(${INCLUDE_DIR}/../bin/root-config --cxxstandard || echo 17)}" + +if ! command -v "${CXX}" >/dev/null; then + echo "Please set CXX to a valid compiler" + exit 2 +fi +if [ ! -d "${INCLUDE_DIR}" ]; then + echo "Usage: ${0} " + echo "Alternatively, set INCLUDE_DIR in the environment" + exit 2 +fi + + +# Check all installed headers for include errors. Some headers cannot be used standalone: +suppressions="TMVA\|vdt" # External +suppressions+="\|RField[A-Z]\|RtypesImp.h\|TAtomicCount[A-Z]\|CladDerivator.h\|TBranchProxyTemplate" # Not to be used standalone +suppressions+="\|TWin32" # Why are these installed in Linux? +suppressions+="\|xRooHypoSpace.h\|xRooFit" # Uses macros to declare namespaces +suppressions+="\|RDaos.h" # Might not be installed +suppressions+="\|RIoUring.hxx" # Might not be installed +suppressions+="\|CPyCppyy/DispatchPtr.h\|CPyCppyy/API.h" # Would need to include Python.h +suppressions+="\|/bvh" # Includes a non-functioning std::span in c++17 +suppressions+="\|cfortran.h" # Seems unable to run with modern compilers +suppressions+="\|hipSYCL.h\|GenVectorX" # Unconditionally installed on Fedora/Ubuntu even if broken +suppressions+="\|TR[A-Z].*__ctors.h" # R interface without any includes, so cannot be parsed as C++ +suppressions+="\|RTaskArena.hxx\|TThreadExecutor.hxx\|TTreeProcessorMT.hxx" # Will raise errors if imt=Off + +HEADERS=$(find "${INCLUDE_DIR}" -type f -name '*.h*' | grep -v "${suppressions}") + +xargs -P ${NCPU:-1} -n 1 "${CXX}" -fsyntax-only -x c++ -std=c++${CXXSTANDARD} ${CXXFLAGS} -I"${INCLUDE_DIR}" <<< "${HEADERS}" + From 4af508f5a9b6225a75d509e8c4a67621b693facb Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Tue, 18 Mar 2025 11:24:56 +0100 Subject: [PATCH 8/8] [CI] Add a post-install header test. After installing, check all headers for syntax errors. This detects headers whose includes can't be resolved. Run the post-install test project if the install step completed successfully. --- .github/workflows/root-ci.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/root-ci.yml b/.github/workflows/root-ci.yml index e6400a2d19e62..1c7b1639cf3d0 100644 --- a/.github/workflows/root-ci.yml +++ b/.github/workflows/root-ci.yml @@ -609,17 +609,22 @@ jobs: ccache -s || true - name: Install - if: ${{ success() && !matrix.is_special }} - run: "cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}" + id: install + run: cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }} + + - name: Check headers + if: steps.install.outcome == 'success' + run: bash test/PostInstall/check-headers.sh ${{ env.INSTALL_DIR }}/include/ - name: Build post-install test project - if: ${{ success() && !matrix.is_special }} + id: postInstall + if: steps.install.outcome == 'success' run: | cmake -S test/PostInstall/ -B ${{ env.POST_INSTALL_DIR }} -DCMAKE_PREFIX_PATH=${{ env.INSTALL_DIR }}; cmake --build ${{ env.POST_INSTALL_DIR }}; - name: CTest in post-install test project - if: ${{ success() && !matrix.is_special }} + if: steps.postInstall.outcome == 'success' && matrix.property != 'asan' working-directory: ${{ env.POST_INSTALL_DIR }} run: ctest --output-on-failure -j $(nproc)