diff --git a/builtins/cfitsio/CMakeLists.txt b/builtins/cfitsio/CMakeLists.txt index c14098bad7f0d..ab9b9839a8d8e 100644 --- a/builtins/cfitsio/CMakeLists.txt +++ b/builtins/cfitsio/CMakeLists.txt @@ -27,12 +27,10 @@ if(WIN32) set(patch_args --ignore-whitespace) endif() -if(NOT EXISTS "${CFITSIO_PREFIX}/src/BUILTIN_CFITSIO/._patched") - set(CFITSIO_PATCH_COMMAND PATCH_COMMAND git init COMMAND git apply ${patch_args} - ${CMAKE_CURRENT_SOURCE_DIR}/cfitsio-no-find-zlib.diff - ${CMAKE_CURRENT_SOURCE_DIR}/no-fortran-wrapper.diff - ${CMAKE_CURRENT_SOURCE_DIR}/cfitsio-marker.diff) -endif() +ROOT_BUILTIN_PATCH_COMMAND(CFITSIO_PATCH_COMMAND + PATCHES ${CMAKE_CURRENT_SOURCE_DIR}/cfitsio-no-find-zlib.diff + ${CMAKE_CURRENT_SOURCE_DIR}/no-fortran-wrapper.diff + GIT_APPLY_ARGS ${patch_args}) ExternalProject_Add( BUILTIN_CFITSIO diff --git a/builtins/cfitsio/cfitsio-marker.diff b/builtins/cfitsio/cfitsio-marker.diff deleted file mode 100644 index 6be2f6ccec01a..0000000000000 --- a/builtins/cfitsio/cfitsio-marker.diff +++ /dev/null @@ -1,4 +0,0 @@ ---- /dev/null 2026-03-12 08:23:04 -+++ ._patched 2026-04-28 08:20:11 -@@ -0,0 +1,1 @@ -+cfitsio has been patched diff --git a/builtins/civetweb/CMakeLists.txt b/builtins/civetweb/CMakeLists.txt index c4d44a39ce686..b3167f9eea0b1 100644 --- a/builtins/civetweb/CMakeLists.txt +++ b/builtins/civetweb/CMakeLists.txt @@ -11,20 +11,14 @@ set(ROOT_CIVETWEB_VERSION 1.16) # with some patches, so almost 1.17 set(ROOT_CIVETWEB_PREFIX ${CMAKE_BINARY_DIR}/builtins/CIVETWEB-prefix) set(ROOT_CIVETWEB_LIBRARY ${ROOT_CIVETWEB_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}civetweb${CMAKE_STATIC_LIBRARY_SUFFIX}) -if (NOT DEFINED GIT_EXECUTABLE) - set(GIT_EXECUTABLE "git") -endif() if(WIN32) set(GIT_PATCH_ARGS "--ignore-space-change") endif() -# check if the patch has already been applied -if(NOT EXISTS "${ROOT_CIVETWEB_PREFIX}/src/BUILTIN_CIVETWEB/._patched") - set(ROOT_CIVETWEB_PATCH_COMMAND PATCH_COMMAND ${GIT_EXECUTABLE} init -b master COMMAND ${GIT_EXECUTABLE} apply ${GIT_PATCH_ARGS} - ${CMAKE_CURRENT_SOURCE_DIR}/1385.patch # https://github.com/civetweb/civetweb/pull/1385 - ${CMAKE_CURRENT_SOURCE_DIR}/1389.patch # https://github.com/civetweb/civetweb/pull/1389 - ${CMAKE_CURRENT_SOURCE_DIR}/1423.patch # https://github.com/civetweb/civetweb/pull/1423 for OpenSSL4 compatibility - ${CMAKE_CURRENT_SOURCE_DIR}/civetweb-marker.diff) -endif() +ROOT_BUILTIN_PATCH_COMMAND(ROOT_CIVETWEB_PATCH_COMMAND + PATCHES ${CMAKE_CURRENT_SOURCE_DIR}/1385.patch # https://github.com/civetweb/civetweb/pull/1385 + ${CMAKE_CURRENT_SOURCE_DIR}/1389.patch # https://github.com/civetweb/civetweb/pull/1389 + ${CMAKE_CURRENT_SOURCE_DIR}/1423.patch # https://github.com/civetweb/civetweb/pull/1423 for OpenSSL4 compatibility + GIT_APPLY_ARGS ${GIT_PATCH_ARGS}) include(ExternalProject) diff --git a/builtins/civetweb/civetweb-marker.diff b/builtins/civetweb/civetweb-marker.diff deleted file mode 100644 index 025de22048d1f..0000000000000 --- a/builtins/civetweb/civetweb-marker.diff +++ /dev/null @@ -1,4 +0,0 @@ ---- /dev/null 2026-03-12 08:23:04 -+++ ._patched 2026-04-28 08:20:11 -@@ -0,0 +1,1 @@ -+civetweb has been patched diff --git a/builtins/ftgl/CMakeLists.txt b/builtins/ftgl/CMakeLists.txt index 9a718ca88fa0d..9370c0dc01fa2 100644 --- a/builtins/ftgl/CMakeLists.txt +++ b/builtins/ftgl/CMakeLists.txt @@ -23,17 +23,9 @@ set(ROOT_FTGL_PATCH_FILE_3 "${CMAKE_CURRENT_SOURCE_DIR}/0003-fix-nullptr.patch") set(ROOT_FTGL_PREFIX ${CMAKE_BINARY_DIR}/builtins/FTGL-prefix) set(ROOT_FTGL_LIBRARY ${ROOT_FTGL_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}ftgl${ROOT_FTGL_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}) -if (NOT DEFINED GIT_EXECUTABLE) - set(GIT_EXECUTABLE "git") -endif() - -if(NOT EXISTS "${ROOT_FTGL_PREFIX}/src/BUILTIN_FTGL/._patched") - set(ROOT_FTGL_PATCH_COMMAND PATCH_COMMAND ${GIT_EXECUTABLE} init COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace - ${ROOT_FTGL_PATCH_FILE_1} - ${ROOT_FTGL_PATCH_FILE_2} - ${ROOT_FTGL_PATCH_FILE_3} - ${CMAKE_CURRENT_SOURCE_DIR}/ftgl-marker.diff) -endif() +ROOT_BUILTIN_PATCH_COMMAND(ROOT_FTGL_PATCH_COMMAND + PATCHES ${ROOT_FTGL_PATCH_FILE_1} ${ROOT_FTGL_PATCH_FILE_2} ${ROOT_FTGL_PATCH_FILE_3} + GIT_APPLY_ARGS --ignore-space-change --ignore-whitespace) include(ExternalProject) diff --git a/builtins/ftgl/ftgl-marker.diff b/builtins/ftgl/ftgl-marker.diff deleted file mode 100644 index 6294690ba8a05..0000000000000 --- a/builtins/ftgl/ftgl-marker.diff +++ /dev/null @@ -1,4 +0,0 @@ ---- /dev/null 2026-03-12 08:23:04 -+++ ._patched 2026-04-28 08:20:11 -@@ -0,0 +1,1 @@ -+ftgl has been patched diff --git a/builtins/gl2ps/CMakeLists.txt b/builtins/gl2ps/CMakeLists.txt index b45b510f302b2..a60d3dce2d6f3 100644 --- a/builtins/gl2ps/CMakeLists.txt +++ b/builtins/gl2ps/CMakeLists.txt @@ -10,7 +10,7 @@ set(ROOT_GL2PS_VERSION 1.4.2) set(ROOT_GL2PS_HASH "8d1c00c1018f96b4b97655482e57dcb0ce42ae2f1d349cd6d4191e7848d9ffe9") # Backport fix for static Win32 having being overwritten by shared lib: https://gitlab.onelab.info/gl2ps/gl2ps/-/work_items/30 # We use all seven commits between 1.4.2 and before bump to 1.4.3 -set(ROOT_GL2PS_PATCH_FILES ${CMAKE_CURRENT_SOURCE_DIR}/v142_post_tag_fixes.patch ${CMAKE_CURRENT_SOURCE_DIR}/gl2ps-marker.patch) +set(ROOT_GL2PS_PATCH_FILES ${CMAKE_CURRENT_SOURCE_DIR}/v142_post_tag_fixes.patch) set(ROOT_GL2PS_PREFIX ${CMAKE_BINARY_DIR}/builtins/GL2PS-prefix) @@ -29,10 +29,7 @@ if(WIN32) set(patch_args --ignore-whitespace) endif() -# check if the patch has already been applied -if(NOT EXISTS "${ROOT_GL2PS_PREFIX}/src/BUILTIN_GL2PS/._patched") - set(GL2PS_PATCH_COMMAND PATCH_COMMAND git init COMMAND git apply ${patch_args} ${ROOT_GL2PS_PATCH_FILES}) -endif() +ROOT_BUILTIN_PATCH_COMMAND(GL2PS_PATCH_COMMAND PATCHES ${ROOT_GL2PS_PATCH_FILES} GIT_APPLY_ARGS ${patch_args}) ExternalProject_Add( BUILTIN_GL2PS diff --git a/builtins/gl2ps/gl2ps-marker.patch b/builtins/gl2ps/gl2ps-marker.patch deleted file mode 100644 index 44b7174ea80ff..0000000000000 --- a/builtins/gl2ps/gl2ps-marker.patch +++ /dev/null @@ -1,4 +0,0 @@ ---- /dev/null 2026-03-12 08:23:04 -+++ ._patched 2026-04-28 08:20:11 -@@ -0,0 +1,1 @@ -+gl2ps has been patched diff --git a/builtins/libgif/CMakeLists.txt b/builtins/libgif/CMakeLists.txt index efb7338762c6f..099dd7989712f 100644 --- a/builtins/libgif/CMakeLists.txt +++ b/builtins/libgif/CMakeLists.txt @@ -28,10 +28,7 @@ if(WIN32) set(patch_args --ignore-whitespace) endif() -# check if the patch has already been applied -if(NOT EXISTS "${ROOT_LIBGIF_PREFIX}/src/BUILTIN_LIBGIF/CMakeLists.txt") - set(LIBGIF_PATCH_COMMAND PATCH_COMMAND git init COMMAND git apply ${patch_args} ${ROOT_LIBGIF_PATCH_FILE}) -endif() +ROOT_BUILTIN_PATCH_COMMAND(LIBGIF_PATCH_COMMAND PATCHES ${ROOT_LIBGIF_PATCH_FILE} GIT_APPLY_ARGS ${patch_args}) ExternalProject_Add( BUILTIN_LIBGIF diff --git a/builtins/unuran/CMakeLists.txt b/builtins/unuran/CMakeLists.txt index 1858fcf19cc91..26619e695740d 100644 --- a/builtins/unuran/CMakeLists.txt +++ b/builtins/unuran/CMakeLists.txt @@ -11,9 +11,8 @@ set(ROOT_UNURAN_HASH "098793854c590b4c2c7e98bc48a45408875f48c5ad47650b5fabbd3e94 set(ROOT_UNURAN_PREFIX ${CMAKE_BINARY_DIR}/builtins/UNURAN-prefix) set(ROOT_UNURAN_LIBRARY ${ROOT_UNURAN_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}unuran${CMAKE_STATIC_LIBRARY_SUFFIX}) set(ROOT_UNURAN_PATCH_FILE_1 "${CMAKE_CURRENT_SOURCE_DIR}/0001-allow-compilation-from-CMake.patch") -if (NOT DEFINED GIT_EXECUTABLE) - set(GIT_EXECUTABLE "git") -endif() +ROOT_BUILTIN_PATCH_COMMAND(ROOT_UNURAN_PATCH_COMMAND PATCHES ${ROOT_UNURAN_PATCH_FILE_1} + GIT_APPLY_ARGS --ignore-space-change --ignore-whitespace) include(ExternalProject) @@ -39,8 +38,7 @@ ExternalProject_Add( URL ${lcgpackages}/unuran-${ROOT_UNURAN_VERSION}.tar.gz https://statmath.wu.ac.at/unuran/unuran-${ROOT_UNURAN_VERSION}.tar.gz URL_HASH SHA256=${ROOT_UNURAN_HASH} - PATCH_COMMAND ${GIT_EXECUTABLE} init -q - COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${ROOT_UNURAN_PATCH_FILE_1} + ${ROOT_UNURAN_PATCH_COMMAND} SOURCE_SUBDIR src LOG_DOWNLOAD TRUE # The following three are disabled to avoid CI failure in Ubuntu22 that has outdated CMake version diff --git a/builtins/xxhash/CMakeLists.txt b/builtins/xxhash/CMakeLists.txt index 9759452bbfdee..fc5d4afe0d638 100644 --- a/builtins/xxhash/CMakeLists.txt +++ b/builtins/xxhash/CMakeLists.txt @@ -15,13 +15,8 @@ set(ROOT_XXHASH_PATCH_FILE ${CMAKE_CURRENT_SOURCE_DIR}/xxhash_add_cmakelists.pat set(ROOT_XXHASH_PREFIX ${CMAKE_BINARY_DIR}/builtins/XXHASH-prefix) -if (NOT DEFINED GIT_EXECUTABLE) - set(GIT_EXECUTABLE "git") -endif() -# check if the patch has already been applied -if(NOT EXISTS "${ROOT_XXHASH_PREFIX}/src/BUILTIN_XXHASH/CMakeLists.txt") - set(XXHASH_PATCH_COMMAND PATCH_COMMAND ${GIT_EXECUTABLE} init COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${ROOT_XXHASH_PATCH_FILE}) -endif() +ROOT_BUILTIN_PATCH_COMMAND(XXHASH_PATCH_COMMAND PATCHES ${ROOT_XXHASH_PATCH_FILE} + GIT_APPLY_ARGS --ignore-space-change --ignore-whitespace) set(ROOT_XXHASH_LIBRARY ${ROOT_XXHASH_PREFIX}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}xxhash${CMAKE_STATIC_LIBRARY_SUFFIX}) diff --git a/cmake/modules/RootMacros.cmake b/cmake/modules/RootMacros.cmake index 3abb71ec5b1f2..5cc523aa360ee 100644 --- a/cmake/modules/RootMacros.cmake +++ b/cmake/modules/RootMacros.cmake @@ -3789,3 +3789,33 @@ function (ROOT_GET_CLANG_LIBRARIES clang_libraries) endforeach(extra_lib) SET(${clang_libraries} "${found_libraries}" PARENT_SCOPE) endfunction(ROOT_GET_CLANG_LIBRARIES) + +#--------------------------------------------------------------------------------------------------- +# ROOT_BUILTIN_PATCH_COMMAND( PATCHES ... [GIT_APPLY_ARGS ...]) +# +# Sets to the PATCH_COMMAND for ExternalProject_Add that applies the given patches, in this +# order, to the sources of a builtin with git apply. +# +# The patch step always starts again from the pristine sources (see +# cmake/scripts/apply_builtin_patches.cmake), so that it doesn't matter why ExternalProject re-runs +# it: after a new download, or because the patch command changed. The command contains a hash of +# the patches, so that also changing the content of a patch re-runs the patch step. +#--------------------------------------------------------------------------------------------------- +function(ROOT_BUILTIN_PATCH_COMMAND var) + cmake_parse_arguments(ARG "" "" "PATCHES;GIT_APPLY_ARGS" ${ARGN}) + set(script ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../scripts/apply_builtin_patches.cmake) + + set(hashes) + foreach(file IN LISTS ARG_PATCHES ITEMS ${script}) + file(SHA256 ${file} hash) + list(APPEND hashes ${hash}) + endforeach() + string(SHA256 hash "${hashes}") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${ARG_PATCHES} ${script}) + + # ExternalProject doesn't replace in the commands, but is + # the same directory, unless LOG_DIR is set. + set(${var} PATCH_COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR= -DSTAMP_DIR= + -DPATCHES_HASH=${hash} -P ${script} -- ${ARG_GIT_APPLY_ARGS} ${ARG_PATCHES} + PARENT_SCOPE) +endfunction() diff --git a/cmake/scripts/apply_builtin_patches.cmake b/cmake/scripts/apply_builtin_patches.cmake new file mode 100644 index 0000000000000..9adc789f300b2 --- /dev/null +++ b/cmake/scripts/apply_builtin_patches.cmake @@ -0,0 +1,113 @@ +# Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. +# All rights reserved. +# +# For the licensing terms see $ROOTSYS/LICENSE. +# For the list of contributors see $ROOTSYS/README/CREDITS. + +# Patch step of the builtins, see ROOT_BUILTIN_PATCH_COMMAND in RootMacros.cmake: +# +# cmake -DSOURCE_DIR= -DSTAMP_DIR= -P apply_builtin_patches.cmake +# -- [...] ... +# +# ExternalProject re-runs the patch step not only on a freshly downloaded source +# tree, but also whenever the patch command changes, and then on the tree that +# is already patched. Therefore, the patches are always applied to the pristine +# sources: the first run commits them to a git repository in the source +# directory (unless the sources are a git clone already), and later runs reset +# the tree to that commit before applying the patches again. + +set(apply_args) +set(patches) +set(after_separator FALSE) +math(EXPR last_arg "${CMAKE_ARGC} - 1") +foreach(i RANGE ${last_arg}) + set(arg "${CMAKE_ARGV${i}}") + if(NOT after_separator) + if(arg STREQUAL "--") + set(after_separator TRUE) + endif() + elseif(arg MATCHES "^-") + list(APPEND apply_args ${arg}) + else() + list(APPEND patches ${arg}) + endif() +endforeach() + +# Git is looked up here and not passed in the patch command, so that a different +# path to git doesn't change the command, which would re-run the patch step. +find_package(Git REQUIRED) + +# Never convert line endings, so that resetting restores the sources as they +# were, and work also in build trees owned by another user. +set(git ${GIT_EXECUTABLE} -c core.autocrlf=false -c core.safecrlf=false -c core.fsmonitor=false + -c gc.auto=0 -c safe.directory=*) + +function(run_git) + execute_process(COMMAND ${git} ${ARGN} WORKING_DIRECTORY ${SOURCE_DIR} COMMAND_ERROR_IS_FATAL ANY) +endfunction() + +# Returns in whether the git command succeeded, without output. +function(try_git result_var) + execute_process(COMMAND ${git} ${ARGN} WORKING_DIRECTORY ${SOURCE_DIR} + RESULT_VARIABLE result OUTPUT_QUIET ERROR_QUIET) + if(result EQUAL 0) + set(${result_var} TRUE PARENT_SCOPE) + else() + set(${result_var} FALSE PARENT_SCOPE) + endif() +endfunction() + +# Left behind if an earlier run was interrupted. +file(REMOVE ${SOURCE_DIR}/.git/index.lock) + +set(has_commit FALSE) +if(EXISTS ${SOURCE_DIR}/.git) + try_git(has_commit rev-parse -q --verify HEAD) +endif() + +if(has_commit) + # Undo the patches of an earlier run. + run_git(reset -q --hard) + run_git(clean -q -d -f -f -x) +else() + # The repository also makes git apply take the paths in the patches relative + # to the source directory, when the build directory is inside a repository. + run_git(init -q) + + # The patches might have been applied already without committing the pristine + # sources, by the patch commands of the builtins before this script was used. + # Undo those that are there, in reverse order. If this doesn't result in the + # pristine sources, applying the patches below fails, and the sources are + # downloaded again with the next build. + set(reversed_patches ${patches}) + list(REVERSE reversed_patches) + foreach(patch IN LISTS reversed_patches) + try_git(reversible apply ${apply_args} --reverse --check ${patch}) + if(reversible) + run_git(apply ${apply_args} --reverse ${patch}) + endif() + endforeach() + + run_git(add -A -f) + run_git(-c user.name=ROOT -c user.email=root@localhost -c commit.gpgsign=false + commit -q --no-verify --allow-empty -m "Pristine sources") +endif() + +# One patch at a time, because git apply would otherwise take the -p level it +# guessed for a patch in the traditional diff format also for the next patches. +foreach(patch IN LISTS patches) + execute_process(COMMAND ${git} apply ${apply_args} ${patch} WORKING_DIRECTORY ${SOURCE_DIR} + RESULT_VARIABLE apply_result) + if(NOT apply_result EQUAL 0) + # The commit might not hold the pristine sources. Removing the stamp of the + # download step makes the next build start again from a new download. + file(GLOB_RECURSE download_stamps ${STAMP_DIR}/*-download) + if(download_stamps) + file(REMOVE ${download_stamps}) + message(FATAL_ERROR "Failed to apply ${patch} to ${SOURCE_DIR}. " + "The sources will be downloaded again with the next build.") + endif() + message(FATAL_ERROR "Failed to apply ${patch} to ${SOURCE_DIR}. " + "Remove that directory to start again from the pristine sources.") + endif() +endforeach()