Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions cmake/modules/ROOTTargetMacros.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ function(generate_target_and_root_library target)
endif()
list(APPEND headers ${habs})
get_filename_component(hName ${habs} NAME)
configure_file(${habs} "${CMAKE_BINARY_DIR}/include/${hName}")
execute_process(
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${habs}" "${CMAKE_BINARY_DIR}/include/${hName}"
OUTPUT_QUIET ERROR_QUIET)
endforeach()


Expand Down Expand Up @@ -249,12 +251,44 @@ function(make_target_root_dictionary target)

set(includeDirs $<REMOVE_DUPLICATES:$<TARGET_PROPERTY:${target},INCLUDE_DIRECTORIES>>)

# add a custom command to generate the dictionary using rootcling
# Write the list of input headers to a cmake file at configure time.
# The build-time stamp script reads this file to compute content hashes,
# avoiding semicolon/shell-escaping issues when passing long lists via -D.
set(_headersListFile ${CMAKE_CURRENT_BINARY_DIR}/${dictionary}_headers_list.cmake)
set(_headersListContent "set(DICT_HEADERS\n")
foreach(_h ${headers})
string(APPEND _headersListContent " \"${_h}\"\n")
endforeach()
string(APPEND _headersListContent ")\n")
file(WRITE "${_headersListFile}.tmp" "${_headersListContent}")
execute_process(
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${_headersListFile}.tmp" "${_headersListFile}"
OUTPUT_QUIET ERROR_QUIET)
file(REMOVE "${_headersListFile}.tmp")

set(stampFile ${CMAKE_CURRENT_BINARY_DIR}/${dictionary}_headers.stamp)

# Step 1: Update stamp only when header CONTENT changes (not just mtime).
# This prevents rootcling from rerunning after git pull or cmake reconfigure
# when header content is actually unchanged.
# cmake-format: off
add_custom_command(
OUTPUT ${stampFile}
COMMAND ${CMAKE_COMMAND}
"-DHEADERS_LIST_FILE=${_headersListFile}"
"-DSTAMP_FILE=${stampFile}"
-P "${CMAKE_SOURCE_DIR}/cmake/modules/update_dict_stamp.cmake"
DEPENDS ${headers}
VERBATIM
COMMENT "Checking header content for ${dictionary}")
# cmake-format: on

# Step 2: Run rootcling only when stamp changes (i.e., header CONTENT changed).
# cmake-format: off
set(space " ")
#message(STATUS " Adding dictionary ${dictionaryFile} to target ${target}")
add_custom_command(
OUTPUT ${dictionaryFile} ${pcmFile} ${rootmapFile}
BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/${pcmBase}
VERBATIM
COMMAND ${CMAKE_COMMAND} -E env "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$ENV{LD_LIBRARY_PATH}"
${ROOT_rootcling_CMD}
Expand All @@ -267,7 +301,7 @@ function(make_target_root_dictionary target)
${headers}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/${pcmBase} ${pcmFile}
COMMAND_EXPAND_LISTS
DEPENDS ${headers})
DEPENDS ${stampFile})
# cmake-format: on

# add dictionary source to the target sources and suppress warnings
Expand Down
48 changes: 48 additions & 0 deletions cmake/modules/update_dict_stamp.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Helper script invoked at build time to update a ROOT dictionary stamp file
# only when the content of the input headers has actually changed.
#
# This breaks the mtime-based dependency chain for rootcling: rather than
# rebuilding dictionaries whenever any header has a newer mtime (e.g., after
# git pull or cmake reconfigure), rootcling only reruns when header content
# actually differs.
#
# Arguments (passed via -D flags):
# HEADERS_LIST_FILE -- path to a cmake file that sets DICT_HEADERS to the
# list of input header paths
# STAMP_FILE -- path to the stamp file to (conditionally) update

if(NOT DEFINED HEADERS_LIST_FILE OR NOT DEFINED STAMP_FILE)
message(FATAL_ERROR
"update_dict_stamp.cmake requires HEADERS_LIST_FILE and STAMP_FILE variables")
endif()

if(NOT EXISTS "${HEADERS_LIST_FILE}")
message(FATAL_ERROR
"Headers list file missing: ${HEADERS_LIST_FILE}\n"
"Re-run cmake to regenerate it: cmake -S <src> -B <build>")
endif()

include("${HEADERS_LIST_FILE}") # sets DICT_HEADERS

# Compute combined SHA256 hash of all input headers.
set(_combined "")
foreach(_h IN LISTS DICT_HEADERS)
if(NOT EXISTS "${_h}")
message(FATAL_ERROR "Header not found: ${_h}")
endif()
file(SHA256 "${_h}" _h_hash)
string(APPEND _combined "${_h}:${_h_hash}\n")
endforeach()
string(SHA256 _new_hash "${_combined}")
set(_new_content "${_new_hash}\n")

# Only rewrite stamp file when the hash has changed.
# Preserving the mtime when content is unchanged prevents rootcling from
# rerunning (the dictionary custom command depends on this stamp's mtime).
set(_old_content "")
if(EXISTS "${STAMP_FILE}")
file(READ "${STAMP_FILE}" _old_content)
endif()
if(NOT _new_content STREQUAL _old_content)
file(WRITE "${STAMP_FILE}" "${_new_content}")
endif()
Loading